Вызывающий поток не может получить доступ к этому объекту, потому что другой поток владеет им

342

Мой код как ниже

public CountryStandards()
{
    InitializeComponent();
    try
    {
        FillPageControls();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Country Standards", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}

/// <summary>
/// Fills the page controls.
/// </summary>
private void FillPageControls()
{
    popUpProgressBar.IsOpen = true;
    lblProgress.Content = "Loading. Please wait...";
    progress.IsIndeterminate = true;
    worker = new BackgroundWorker();
    worker.DoWork += new System.ComponentModel.DoWorkEventHandler(worker_DoWork);
    worker.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(worker_ProgressChanged);
    worker.WorkerReportsProgress = true;
    worker.WorkerSupportsCancellation = true;
    worker.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
    worker.RunWorkerAsync();                    
}

private void worker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
    GetGridData(null, 0); // filling grid
}

private void worker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
    progress.Value = e.ProgressPercentage;
}

private void worker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
    worker = null;
    popUpProgressBar.IsOpen = false;
    //filling Region dropdown
    Standards.UDMCountryStandards objUDMCountryStandards = new Standards.UDMCountryStandards();
    objUDMCountryStandards.Operation = "SELECT_REGION";
    DataSet dsRegionStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
    if (!StandardsDefault.IsNullOrEmptyDataTable(dsRegionStandards, 0))
        StandardsDefault.FillComboBox(cmbRegion, dsRegionStandards.Tables[0], "Region", "RegionId");

    //filling Currency dropdown
    objUDMCountryStandards = new Standards.UDMCountryStandards();
    objUDMCountryStandards.Operation = "SELECT_CURRENCY";
    DataSet dsCurrencyStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
    if (!StandardsDefault.IsNullOrEmptyDataTable(dsCurrencyStandards, 0))
        StandardsDefault.FillComboBox(cmbCurrency, dsCurrencyStandards.Tables[0], "CurrencyName", "CurrencyId");

    if (Users.UserRole != "Admin")
        btnSave.IsEnabled = false;

}

/// <summary>
/// Gets the grid data.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="pageIndex">Index of the page.( used in case of paging)   </pamam>
private void GetGridData(object sender, int pageIndex)
{
    Standards.UDMCountryStandards objUDMCountryStandards = new Standards.UDMCountryStandards();
    objUDMCountryStandards.Operation = "SELECT";
    objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;
    DataSet dsCountryStandards = objStandardsBusinessLayer.GetCountryStandards(objUDMCountryStandards);
    if (!StandardsDefault.IsNullOrEmptyDataTable(dsCountryStandards, 0) && (chkbxMarketsSearch.IsChecked == true || chkbxBudgetsSearch.IsChecked == true || chkbxProgramsSearch.IsChecked == true))
    {
        DataTable objDataTable = StandardsDefault.FilterDatatableForModules(dsCountryStandards.Tables[0], "Country", chkbxMarketsSearch, chkbxBudgetsSearch, chkbxProgramsSearch);
        dgCountryList.ItemsSource = objDataTable.DefaultView;
    }
    else
    {
        MessageBox.Show("No Records Found", "Country Standards", MessageBoxButton.OK, MessageBoxImage.Information);
        btnClear_Click(null, null);
    }
}

Шаг objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;в получении данных сетки вызывает исключение

Вызывающий поток не может получить доступ к этому объекту, потому что другой поток владеет им.

Что здесь не так?

Кунтады Нитеш
источник

Ответы:

699

Это общая проблема с людьми, начинающими. Всякий раз, когда вы обновляете свои элементы пользовательского интерфейса из потока, отличного от основного потока, вам необходимо использовать:

this.Dispatcher.Invoke(() =>
{
    ...// your code here.
});

Вы также можете использовать, control.Dispatcher.CheckAccess()чтобы проверить, принадлежит ли текущий поток элемент управления. Если он владеет им, ваш код выглядит как обычно. В противном случае используйте вышеуказанный шаблон.

Кандид
источник
3
У меня та же проблема, что и у ОП; Моя проблема сейчас в том, что событие вызывает переполнение стека. : \
Малавос
2
Вернулся к своему старому проекту и решил это. Кроме того, я забыл +1 это. Этот метод работает довольно хорошо! Это улучшает время загрузки моего приложения на 10 секунд или даже больше, просто используя потоки для загрузки наших локализованных ресурсов. Ура!
Малавос
4
Если я не ошибаюсь, вы даже не можете прочитать объект пользовательского интерфейса из потока, не принадлежащего владельцу; удивил меня немного.
Эллиот
32
Application.Current.Dispatcher.Invoke(MyMethod, DispatcherPriority.ContextIdle);получить диспетчер, если не в потоке пользовательского интерфейса согласно этому ответу
JumpingJezza
2
+1. Ха! Я использовал это для некоторых хакеров WPF, чтобы не связывать вещи. Я был в статическом контексте, поэтому я не мог использовать this.Dispatcher.Invoke.... вместо этого ... myControl.Dispatcher.Invoke:) Мне нужно было вернуть объект обратно, поэтому я сделал myControlDispatcher.Invoke<object>(() => myControl.DataContext);
C. Tewalt
53

Еще одно хорошее использование для Dispatcher.Invokeнемедленного обновления пользовательского интерфейса в функции, которая выполняет другие задачи:

// Force WPF to render UI changes immediately with this magic line of code...
Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle);

Я использую это, чтобы обновить текст кнопки на « Обработка ... » и отключить ее при выполнении WebClientзапросов.

computerGuyCJ
источник
4
Этот ответ обсуждается на Meta. meta.stackoverflow.com/questions/361844/…
JDB до сих пор помнит Монику
Это мешало моему контролю получать данные из интернета?
Васим Ахмад Наим
41

Чтобы добавить мои 2 цента, может возникнуть исключение, даже если вы вызываете свой код до конца System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke().
Дело в том , что вы должны вызвать Invoke()из Dispatcher-за контроля , который вы пытаетесь получить доступ , которые в некоторых случаях не может быть такой же , как System.Windows.Threading.Dispatcher.CurrentDispatcher. Так что вместо этого вы должны использовать, YourControl.Dispatcher.Invoke()чтобы быть в безопасности. Я несколько часов колотил головой, прежде чем понял это.

Обновить

Для будущих читателей, похоже, что это изменилось в более новых версиях .NET (4.0 и выше). Теперь вам больше не нужно беспокоиться о правильном диспетчере при обновлении свойств поддержки пользовательского интерфейса в вашей виртуальной машине. Движок WPF будет маршалировать вызовы между потоками в правильном потоке пользовательского интерфейса. Смотрите подробности здесь . Спасибо @aaronburro за информацию и ссылку. Вы также можете прочитать наш разговор ниже в комментариях.

Dotnet
источник
4
@ l33t: WPF поддерживает несколько потоков пользовательского интерфейса в одном приложении, каждый из которых будет иметь свои собственные Dispatcher. В тех случаях (которые по общему признанию редки), вызов Control.Dispatcher- безопасный подход. Для справки вы можете увидеть эту статью, а также этот пост (в частности, ответ Squidward).
dotNET
1
Интересно, что я столкнулся с этим самым исключением, когда я погуглил и приземлился на этой странице и, как и большинство из нас, попробовал ответ с наибольшим количеством голосов, который не решил мою проблему тогда. Затем я выяснил эту причину и разместил ее здесь для разработчиков.
dotNET
1
@ l33t, если вы используете MVVM правильно, то это не должно быть проблемой. Представление обязательно знает, какой Dispatcher он использует, в то время как ViewModels и Models ничего не знают об элементах управления и не должны знать об элементах управления.
Ааронберро
1
@aaronburro: проблема в том, что VM может захотеть запускать действия на альтернативных потоках (например, задачи, действия на основе таймера, параллельные запросы), а по мере выполнения операции может обновлять пользовательский интерфейс (через RaisePropertyChanged и т. д.), который в свою очередь попытается для доступа к элементу управления пользовательского интерфейса из потока, не являющегося пользовательским интерфейсом, и, следовательно, к этому исключению Я не знаю правильного подхода MVVM, который бы решил эту проблему.
dotNET
1
Механизм привязки WPF автоматически изменяет события изменения свойства на правильный диспетчер. Вот почему ВМ не нужно знать о Диспетчере; все, что нужно сделать, это просто поднять события, измененные свойством. Привязка WinForms - это отдельная история.
Ааронберро
34

Если вы столкнулись с этой проблемой и элементы управления пользовательским интерфейсом были созданы в отдельном рабочем потоке при работе с BitmapSourceили ImageSourceв WPF, Freeze()сначала вызовите метод перед передачей BitmapSourceили ImageSourceв качестве параметра любому методу. Использование Application.Current.Dispatcher.Invoke()не работает в таких случаях

ЮФО
источник
24
Ах, ничего похожего на старый добрый расплывчатый и таинственный трюк, чтобы решить что-то, что никто не понимает.
Эдвин
2
Я хотел бы получить больше информации о том, почему это работает и как я мог бы это выяснить сам.
Ксавье Шей,
25

это случилось со мной , потому что я пытался access UIв компонентеanother thread insted of UI thread

как это

private void button_Click(object sender, RoutedEventArgs e)
{
    new Thread(SyncProcces).Start();
}

private void SyncProcces()
{
    string val1 = null, val2 = null;
    //here is the problem 
    val1 = textBox1.Text;//access UI in another thread
    val2 = textBox2.Text;//access UI in another thread
    localStore = new LocalStore(val1);
    remoteStore = new RemoteStore(val2);
}

чтобы решить эту проблему, оберните любой вызов пользовательского интерфейса в то, что Кандид упомянул выше в своем ответе

private void SyncProcces()
{
    string val1 = null, val2 = null;
    this.Dispatcher.Invoke((Action)(() =>
    {//this refer to form in WPF application 
        val1 = textBox.Text;
        val2 = textBox_Copy.Text;
    }));
    localStore = new LocalStore(val1);
    remoteStore = new RemoteStore(val2 );
}
Башир Аль-Момани
источник
1
Принято решение, потому что это не дублирующий ответ или плагиат, но вместо этого он дает хороший пример, которого не хватало другим ответам, в то же время отдавая должное тому, что было опубликовано ранее.
Panzercrisis
Upvote для четкого ответа. Хотя то же самое было написано другими, но это дает понять всем, кто застрял.
NishantM
15

По какой-то причине ответ Кэндиды не получил. Это было полезно, однако, поскольку это привело меня к поиску этого, который работал отлично:

System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
{
   //your code here...
}));
Сара
источник
Возможно, вы не звонили из класса формы. Либо вы можете получить ссылку на окно, либо вы можете использовать то, что вы предложили.
Симона
4
Если это сработало для вас, было ненужным использовать его в первую очередь. System.Windows.Threading.Dispatcher.CurrentDispatcherявляется диспетчером для текущего потока . Это означает, что если вы находитесь в фоновом потоке, он не будет диспетчером потока пользовательского интерфейса. Чтобы получить доступ к диспетчеру потока пользовательского интерфейса, используйте System.Windows.Application.Current.Dispatcher.
13

Вам необходимо обновить интерфейс пользователя, поэтому используйте

Dispatcher.BeginInvoke(new Action(() => {GetGridData(null, 0)})); 
VikramBose
источник
4

Это работает для меня.

new Thread(() =>
        {

        Thread.CurrentThread.IsBackground = false;
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate {

          //Your Code here.

        }, null);
        }).Start();
nPcomp
источник
3

Я также обнаружил, что System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()не всегда диспетчер контроля цели, как писал dotNet в своем ответе. У меня не было доступа к собственному диспетчеру управления, поэтому я использовал, Application.Current.Dispatcherи это решило проблему.

Паулюс Лимма
источник
2

Проблема в том, что вы звоните GetGridDataиз фонового потока. Этот метод обращается к нескольким элементам управления WPF, которые связаны с основным потоком. Любая попытка доступа к ним из фонового потока приведет к этой ошибке.

Чтобы вернуться к правильной теме, вы должны использовать SynchronizationContext.Current.Post. Однако в данном конкретном случае кажется, что большая часть работы, которую вы выполняете, основана на пользовательском интерфейсе. Следовательно, вы будете создавать фоновый поток, чтобы сразу вернуться к потоку пользовательского интерфейса и выполнить некоторую работу. Вам нужно немного реорганизовать свой код, чтобы он мог выполнять дорогостоящую работу в фоновом потоке, а затем публиковать новые данные в потоке пользовательского интерфейса.

JaredPar
источник
2

Как уже упоминалось здесь , Dispatcher.Invokeможет заморозить UI. Следует использовать Dispatcher.BeginInvokeвместо.

Вот удобный класс расширения для упрощения проверки и вызова диспетчера вызовов.

Пример использования: (вызов из окна WPF)

this Dispatcher.InvokeIfRequired(new Action(() =>
{
    logTextbox.AppendText(message);
    logTextbox.ScrollToEnd();
}));

Класс расширения:

using System;
using System.Windows.Threading;

namespace WpfUtility
{
    public static class DispatcherExtension
    {
        public static void InvokeIfRequired(this Dispatcher dispatcher, Action action)
        {
            if (dispatcher == null)
            {
                return;
            }
            if (!dispatcher.CheckAccess())
            {
                dispatcher.BeginInvoke(action, DispatcherPriority.ContextIdle);
                return;
            }
            action();
        }
    }
}
Джесон Мартаджая
источник
0

Кроме того, другое решение заключается в том, что ваши элементы управления создаются в потоке пользовательского интерфейса, а не, например, в фоновом рабочем потоке.

FindOutIslamNow
источник
0

Я продолжал получать сообщение об ошибке, когда я добавил каскадные списки в свое приложение WPF, и исправил ошибку с помощью этого API:

    using System.Windows.Data;

    private readonly object _lock = new object();
    private CustomObservableCollection<string> _myUiBoundProperty;
    public CustomObservableCollection<string> MyUiBoundProperty
    {
        get { return _myUiBoundProperty; }
        set
        {
            if (value == _myUiBoundProperty) return;
            _myUiBoundProperty = value;
            NotifyPropertyChanged(nameof(MyUiBoundProperty));
        }
    }

    public MyViewModelCtor(INavigationService navigationService) 
    {
       // Other code...
       BindingOperations.EnableCollectionSynchronization(AvailableDefectSubCategories, _lock );

    }

Для получения дополнительной информации см. Https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Windows.Data.BindingOperations.EnableCollectionSynchronization);k(TargetFrameworkMoniker-.NETFramework,Version % 3Dv4.7); к (DevLang-CSharp) & й = TRUE

user8128167
источник