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?

Kuntady Nithesh
źródło

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.

Candide
źródło
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ń.

computerGuyCJ
źródło
4
Ta odpowiedź jest dyskutowana na Meta. meta.stackoverflow.com/questions/361844/…
JDB wciąż pamięta Monikę
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.

dotNET
źródło
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

juFo
źródło
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 );
}
Basheer AL-MOMANI
źródło
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...
}));
Sarah
źródło
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)})); 
VikramBose
źródło
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();
nPcomp
źródło
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.

Paulus Limma
źródło
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

JaredPar
źródło
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();
        }
    }
}
Jeson Martajaya
źródło
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.

FindOutIslamNow
źródło
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

użytkownik8128167
źródło