Викликає потік не може отримати доступ до цього об'єкта, оскільки інший потік йому належить


341

Мій код як нижче

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;отримання даних сітки кидає виняток

Викликає потік не може отримати доступ до цього об’єкта, оскільки інший потік йому належить.

Що тут не так?


Відповіді:


698

Це звичайна проблема людей, що починають працювати. Щоразу, коли ви оновлюєте елементи інтерфейсу користувача з потоку, відмінного від основного, потрібно використовувати:

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запитів.


4
Ця відповідь обговорюється на Meta. meta.stackoverflow.com/questions/361844/…
JDB все ще пам’ятає Моніку

Це зупинило мій контроль від отримання даних з Інтернету?
Waseem Ahmad Naeem

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, це не повинно бути проблемою. Перегляд обов'язково знає, яким диспетчером він користується, тоді як ViewModels і Моделі нічого не знають про елементи керування і не повинні знати про елементи управління.
aaronburro

1
@aaronburro: Проблема полягає в тому, що VM може захотіти запускати дії на альтернативних потоках (наприклад, Завдання, дії, засновані на таймері, паралельні запити), і в міру прогресування операції, можливо, захоче оновити інтерфейс користувача (через RaisePropertyChanged тощо), що, в свою чергу, спробує отримати доступ до елемента керування користувальницьким інтерфейсом з потоку, який не є користувальницьким інтерфейсом, і, таким чином, це виняток. Я не знаю правильного підходу MVVM, який би вирішив цю проблему.
dotNET

1
Двигун прив’язки WPF автоматично переводить події зміни властивостей у правильний диспетчер. Ось чому В.М. не потрібно знати диспетчера; все, що потрібно зробити, це лише підняти події, що змінилися на власність. Прив’язка WinForms - це інша історія.
aaronburro

34

Якщо ви зіткнулися з цією проблемою, і елементи керування користувальницьким інтерфейсом були створені на окремій робочій нитці під час роботи з BitmapSourceабо ImageSourceв WPF, Freeze()спочатку викличте метод, перш ніж передавати BitmapSourceабо ImageSourceяк параметр будь-якому методу. Використання Application.Current.Dispatcher.Invoke()не працює в таких випадках


24
Ах, нічого подібного на старий добрий розпливчастий та загадковий трюк, щоб вирішити щось, ніхто не розуміє.
Едвін

2
Мені б хотілося більше інформації, чому це працює, і як я міг би сам це зрозуміти.
Xavier Shay


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

щоб вирішити цю проблему, загорніть будь-який дзвінок в інтерфейс, що Candide згадував вище у своїй відповіді

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може застигнути інтерфейс користувача. Слід використовувати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,, % 3Dv4.7); k (DevLang-csharp) & rd = вірно

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.