Wątek wywołujący nie może uzyskać dostępu do tego obiektu, ponieważ inny wątek jest jego właścicielem


341

Mój kod jest jak poniżej

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

Krok objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;w uzyskaniu danych siatki generuje wyjątek

Wątek wywołujący nie może uzyskać dostępu do tego obiektu, ponieważ inny wątek jest jego właścicielem.

Co tu jest nie tak?


Odpowiedzi:


698

Jest to powszechny problem z rozpoczynaniem pracy przez ludzi. Ilekroć aktualizujesz elementy interfejsu użytkownika z wątku innego niż główny wątek, musisz użyć:

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

Możesz także użyć control.Dispatcher.CheckAccess()do sprawdzenia, czy bieżący wątek jest właścicielem kontrolki. Jeśli jest właścicielem, kod wygląda normalnie. W przeciwnym razie użyj powyższego wzoru.


3
Mam ten sam problem co OP; Mój problem polega na tym, że to zdarzenie powoduje teraz przepełnienie stosu. : \
— Malavos

2
Wróciłem do mojego starego projektu i rozwiązałem ten problem. Poza tym zapomniałem dać +1. Ta metoda działa całkiem dobrze! Poprawia mój czas ładowania aplikacji na 10 sekund lub nawet dłużej, po prostu za pomocą wątków, aby załadować nasze zlokalizowane zasoby. Twoje zdrowie!
— Malavos

4
Jeśli się nie mylę, nie można nawet odczytać obiektu interfejsu użytkownika z wątku niebędącego właścicielem; trochę mnie zaskoczyło.
— Elliot

32
Application.Current.Dispatcher.Invoke(MyMethod, DispatcherPriority.ContextIdle);dostać dyspozytora, jeśli nie w wątku interfejsu użytkownika, zgodnie z tą odpowiedzią
— JumpingJezza

2
+1. Ha! Użyłem tego do niektórych działań hakerskich WPF, aby zachować niezależność. Byłem w kontekście statycznym, więc nie mogłem użyć this.Dispatcher.Invoke.... zamiast tego ... myControl.Dispatcher.Invoke:) Musiałem zwrócić obiekt, więc zrobiłem to myControlDispatcher.Invoke<object>(() => myControl.DataContext);
— C. Tewalt

53

Innym dobrym zastosowaniem Dispatcher.Invokejest natychmiastowa aktualizacja interfejsu użytkownika w funkcji, która wykonuje inne zadania:

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

Używam tego, aby zaktualizować tekst przycisku do „ Przetwarzanie ... ” i wyłączyć go podczas WebClientwysyłania żądań.



To powstrzymało moją kontrolę przed pobieraniem danych z Internetu?
— Waseem Ahmad Naeem

41

Aby dodać moje 2 centy, wyjątek może wystąpić nawet po wywołaniu kodu System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke() .
Chodzi o to, że trzeba zadzwonić Invoke()z Dispatchertego sterowania, który próbujesz dostępu , które w niektórych przypadkach mogą nie być takie same jak System.Windows.Threading.Dispatcher.CurrentDispatcher. Zamiast tego powinieneś użyćYourControl.Dispatcher.Invoke() aby być bezpiecznym. Waliłem głową przez kilka godzin, zanim zdałem sobie z tego sprawę.

Aktualizacja

Dla przyszłych czytelników wygląda na to, że zmieniło się to w nowszych wersjach .NET (4.0 i nowszych). Teraz nie musisz się już martwić o poprawność programu rozsyłającego podczas aktualizowania właściwości tworzenia kopii zapasowych interfejsu użytkownika na maszynie wirtualnej. Silnik WPF będzie kierował wywołania wątków we właściwym wątku interfejsu użytkownika. Zobacz więcej szczegółów tutaj . Dzięki @aaronburro za informacje i link. Możesz również przeczytać naszą rozmowę poniżej w komentarzach.


4
@ l33t: WPF obsługuje wiele wątków interfejsu użytkownika w jednej aplikacji, z których każdy będzie miał własny Dispatcher. W tych przypadkach (które są co prawda rzadkie) dzwonienie Control.Dispatcherjest bezpiecznym podejściem. W celach informacyjnych możesz zobaczyć ten artykuł, a także ten post SO (szczególnie odpowiedź Skalmar).
— dotNET

1
Co ciekawe, spotkałem się z tym wyjątkiem, kiedy przejrzałem Google i wylądowałem na tej stronie i, jak większość z nas, wypróbowałem najwyższą głosowaną odpowiedź, która wtedy nie rozwiązała mojego problemu. Następnie odkryłem ten powód i opublikowałem go tutaj dla programistów.
— dotNET

1
@ l33t, jeśli poprawnie używasz MVVM, nie powinno to stanowić problemu. Widok musi koniecznie wiedzieć, z jakiego korzysta programu rozsyłającego, podczas gdy modele ViewModels i modele nic nie wiedzą o elementach sterujących i nie muszą wiedzieć o elementach sterujących.
— aaronburro

1
@aaronburro: Problem polega na tym, że VM może chcieć uruchamiać akcje w alternatywnych wątkach (np. Zadania, akcje oparte na zegarze, zapytania równoległe), aw miarę postępu operacji może chcieć zaktualizować interfejs użytkownika (poprzez RaisePropertyChanged itp.), który z kolei spróbuje aby uzyskać dostęp do kontrolki interfejsu użytkownika z wątku innego niż interfejs użytkownika, a tym samym spowodować ten wyjątek. Nie znam prawidłowego podejścia MVVM , które rozwiązałoby ten problem.
— dotNET

1
Mechanizm wiązania WPF automatycznie przekazuje zdarzenia zmiany właściwości do właściwego programu rozsyłającego. Właśnie dlatego VM nie musi wiedzieć o Dyspozytorze; wszystko, co musi zrobić, to po prostu wywołać zdarzenia związane ze zmianą własności. Wiązanie WinForms to inna historia.
— aaronburro

34

Jeśli napotkasz ten problem i formanty interfejsu użytkownika zostały utworzone w osobnym wątku roboczym podczas pracy z WPF BitmapSourcelub ImageSourcew nim, wywołaj Freeze()metodę najpierw przed przekazaniem parametru BitmapSourcelub ImageSourcejako parametru do dowolnej metody. Używanie Application.Current.Dispatcher.Invoke()nie działa w takich przypadkach


24
Ach, nie ma to jak stara, mglista i tajemnicza sztuczka, aby rozwiązać coś, czego nikt nie rozumie.
— Edwin,

2
Chciałbym uzyskać więcej informacji o tym, dlaczego to działa i jak sam mogłem to wymyślić.
— Xavier Shay


25

stało się to ze mną, ponieważ próbowałem się access UIwłączyćanother thread insted of UI thread

lubię to

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

aby rozwiązać ten problem, zawiń każde wywołanie interfejsu użytkownika wewnątrz tego, co Candide wspomniał powyżej w swojej odpowiedzi

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
Pozytywnie ocenione, ponieważ nie jest to duplikat ani plagiat, ale stanowi dobry przykład, że brakowało innych odpowiedzi, jednocześnie uznając to, co zostało opublikowane wcześniej.
— Panzercrisis,

Upvote służy jednoznacznej odpowiedzi. Chociaż to samo zostało napisane przez innych, ale wyjaśnia to każdemu, kto utknął.
— NishantM

15

Z jakiegoś powodu odpowiedź Kandyda nie została zbudowana. Było to jednak pomocne, ponieważ doprowadziło mnie do znalezienia tego, co działało idealnie:

System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
{
   //your code here...
}));

Możliwe, że nie zadzwoniłeś z klasy formularza. Albo możesz pobrać odniesienie do okna, albo prawdopodobnie możesz użyć tego, co zasugerowałeś.
— Simone,

4
Jeśli to działało, nie trzeba go używać. System.Windows.Threading.Dispatcher.CurrentDispatcherjest dyspozytorem bieżącego wątku . Oznacza to, że jeśli jesteś w wątku w tle, nie będzie to dyspozytor wątku interfejsu użytkownika. Aby uzyskać dostęp do programu rozsyłającego wątek interfejsu użytkownika, użyj System.Windows.Application.Current.Dispatcher.

13

Musisz zaktualizować interfejs użytkownika, więc użyj

Dispatcher.BeginInvoke(new Action(() => {GetGridData(null, 0)})); 

4

To działa dla mnie.

new Thread(() =>
        {

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

          //Your Code here.

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

3

Odkryłem również, że System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()nie zawsze jest to dyspozytor kontroli celu, tak jak napisał dotNet w swojej odpowiedzi. Nie miałem dostępu do własnego dyspozytora kontroli, więc skorzystałem Application.Current.Dispatcheri to rozwiązało problem.


2

Problem polega na tym, że dzwonisz GetGridDataz wątku w tle. Ta metoda umożliwia dostęp do kilku elementów sterujących WPF, które są powiązane z głównym wątkiem. Każda próba dostępu do nich z wątku w tle prowadzi do tego błędu.

Aby wrócić do właściwego wątku, powinieneś użyć SynchronizationContext.Current.Post. Jednak w tym konkretnym przypadku wydaje się, że większość pracy, którą wykonujesz, oparta jest na interfejsie użytkownika. W związku z tym należy utworzyć wątek w tle, aby natychmiast wrócić do wątku interfejsu użytkownika i wykonać trochę pracy. Musisz nieco zmienić kod, aby mógł wykonać kosztowną pracę w wątku w tle, a następnie opublikować nowe dane w wątku interfejsu użytkownika


2

Jak wspomniano tutaj , Dispatcher.Invokemoże zamrozić interfejs użytkownika. Powinien użyć Dispatcher.BeginInvokezamiast.

Oto przydatna klasa rozszerzeń, która upraszcza sprawdzanie i wywoływanie wywołań programu rozsyłającego.

Przykładowe użycie: (połączenie z okna WPF)

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

Klasa rozszerzenia:

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

Innym rozwiązaniem jest zapewnienie, że formanty są tworzone w wątku interfejsu użytkownika, a nie na przykład w wątku procesu roboczego w tle.


0

Wciąż pojawiał się błąd, gdy dodawałem kaskadowe kombinacje do mojej aplikacji WPF i rozwiązałem ten błąd, używając tego interfejsu 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 );

    }

Aby uzyskać szczegółowe informacje, zobacz 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); k (DevLang-csharp) & rd = true

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.