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


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запросов.



Это мешало моему контролю получать данные из интернета?
— Васим Ахмад Наим

41

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

Обновить

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


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)})); 

4

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

new Thread(() =>
        {

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

          //Your Code here.

        }, null);
        }).Start();

3

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


2

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

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


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

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


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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.