Der aufrufende Thread kann nicht auf dieses Objekt zugreifen, da es einem anderen Thread gehört

341

Mein Code ist wie folgt

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

Der Schritt objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;zum Abrufen von Rasterdaten löst eine Ausnahme aus

Der aufrufende Thread kann nicht auf dieses Objekt zugreifen, da es einem anderen Thread gehört.

Was ist hier los?

Kuntady Nithesh
quelle

Antworten:

697

Dies ist ein häufiges Problem bei den ersten Schritten. Wann immer Sie Ihre UI-Elemente von einem anderen Thread als dem Haupt-Thread aktualisieren, müssen Sie Folgendes verwenden:

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

Sie können auch control.Dispatcher.CheckAccess()überprüfen, ob der aktuelle Thread das Steuerelement besitzt. Wenn es es besitzt, sieht Ihr Code wie gewohnt aus. Verwenden Sie andernfalls das obige Muster.

Candide
quelle
3
Ich habe das gleiche Problem wie OP; Mein Problem ist jetzt, dass das Ereignis jetzt einen Stapelüberlauf verursacht. : \
Malavos
2
Ging zurück zu meinem alten Projekt und löste dies. Außerdem hatte ich vergessen, dies zu +1. Diese Methode funktioniert ganz gut! Es verbessert die Ladezeit meiner Anwendung auf 10 Sekunden oder sogar mehr, indem nur Threads zum Laden unserer lokalisierten Ressourcen verwendet werden. Prost!
Malavos
4
Wenn ich mich nicht irre, können Sie nicht einmal ein UI-Objekt aus einem Nicht-Eigentümer-Thread lesen. hat mich ein bisschen überrascht.
Elliot
32
Application.Current.Dispatcher.Invoke(MyMethod, DispatcherPriority.ContextIdle);um den Dispatcher zu erhalten, wenn nicht im UI-Thread gemäß dieser Antwort
JumpingJezza
2
+1. Ha! Ich habe dies für einige WPF-Hacker verwendet, um die Dinge entkoppelt zu halten. Ich war in einem statischen Kontext, also konnte ich nicht verwenden this.Dispatcher.Invoke... stattdessen ... myControl.Dispatcher.Invoke:) Ich musste ein Objekt zurückgeben, also tat ich es myControlDispatcher.Invoke<object>(() => myControl.DataContext);
C. Tewalt
52

Eine weitere gute Verwendung Dispatcher.Invokeist das sofortige Aktualisieren der Benutzeroberfläche in einer Funktion, die andere Aufgaben ausführt:

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

Ich verwende dies, um den Schaltflächentext auf " Verarbeitung ... " zu aktualisieren und ihn zu deaktivieren, während ich WebClientAnfragen stelle.

computerGuyCJ
quelle
4
Diese Antwort wird auf Meta diskutiert. meta.stackoverflow.com/questions/361844/…
JDB erinnert sich noch an Monica
Dies hat meine Kontrolle daran gehindert, Daten aus dem Internet abzurufen.
Waseem Ahmad Naeem
41

Um meine 2 Cent hinzuzufügen, kann die Ausnahme auch dann auftreten, wenn Sie Ihren Code durchrufen System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke().
Der Punkt ist , dass Sie anrufen müssen Invoke()von der Dispatcherder Kontrolle , dass Sie zugreifen möchten , die in einigen Fällen als nicht gleich sein System.Windows.Threading.Dispatcher.CurrentDispatcher. Also sollten Sie stattdessen verwenden YourControl.Dispatcher.Invoke(), um sicher zu sein. Ich schlug mir ein paar Stunden lang den Kopf, bevor ich das merkte.

Aktualisieren

Für zukünftige Leser scheint sich dies in den neueren Versionen von .NET (4.0 und höher) geändert zu haben. Jetzt müssen Sie sich nicht mehr um den richtigen Dispatcher kümmern, wenn Sie die UI-Backing-Eigenschaften in Ihrer VM aktualisieren. Die WPF-Engine führt Cross-Thread-Aufrufe für den richtigen UI-Thread durch. Weitere Details finden Sie hier . Danke an @aaronburro für die Infos und den Link. Vielleicht möchten Sie auch unser Gespräch unten in Kommentaren lesen.

Punkt net
quelle
4
@ l33t: WPF unterstützt mehrere UI-Threads in einer Anwendung, von denen jeder seinen eigenen hat Dispatcher. In diesen Fällen (die zugegebenermaßen selten sind) ist das Anrufen Control.Dispatcherder sichere Ansatz. Als Referenz können Sie diesen Artikel sowie diesen SO-Beitrag sehen (insbesondere die Antwort von Thaddäus).
dotNET
1
Interessanterweise war ich mit genau dieser Ausnahme konfrontiert, als ich auf dieser Seite gegoogelt und gelandet bin und wie die meisten von uns die Antwort mit der höchsten Stimme ausprobiert habe, die mein Problem damals nicht gelöst hat. Ich habe dann diesen Grund herausgefunden und ihn hier für Peer-Entwickler gepostet.
dotNET
1
@ l33t, wenn Sie MVVM richtig verwenden, sollte es kein Problem sein. Die Ansicht muss unbedingt wissen, welchen Dispatcher sie verwendet, während die ViewModels und Modelle nichts über Steuerelemente wissen und keine Steuerelemente kennen müssen.
Aaronburro
1
@aaronburro: Das Problem besteht darin, dass VM möglicherweise Aktionen für alternative Threads starten möchte (z. B. Aufgaben, Timer-basierte Aktionen, parallele Abfragen) und im Verlauf des Vorgangs möglicherweise die Benutzeroberfläche aktualisieren möchte (über RaisePropertyChanged usw.), was wiederum versucht um von einem Nicht-UI-Thread auf ein UI-Steuerelement zuzugreifen und somit zu dieser Ausnahme zu führen. Ich kenne keinen korrekten MVVM-Ansatz , der dieses Problem lösen würde.
DotNET
1
Die WPF-Bindungs-Engine führt Eigenschaftsänderungsereignisse automatisch an den richtigen Dispatcher weiter. Aus diesem Grund muss VM den Dispatcher nicht kennen. Alles, was es tun muss, ist, Ereignisse mit geänderten Eigenschaften auszulösen. Die WinForms-Bindung ist eine andere Geschichte.
Aaronburro
34

Wenn Sie auf dieses Problem stoßen und UI-Steuerelemente bei der Arbeit mit oder in WPF in einem separaten Arbeitsthread erstellt wurden , rufen Sie zuerst die Methode auf, bevor Sie das oder als Parameter an eine Methode übergeben. Die Verwendung funktioniert in solchen Fällen nichtBitmapSourceImageSourceFreeze()BitmapSourceImageSourceApplication.Current.Dispatcher.Invoke()

juFo
quelle
24
Ah, nichts wie ein guter alter vager und mysteriöser Trick, um etwas zu lösen, das niemand versteht.
Edwin
2
Ich würde gerne mehr Informationen darüber erhalten, warum dies funktioniert und wie ich es selbst hätte herausfinden können.
Xavier Shay
25

Das ist mir passiert, weil ich versucht habe, mich access UIeinzumischenanother thread insted of UI thread

so was

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

Um dieses Problem zu lösen, wickeln Sie jeden UI-Anruf in das ein, was Candide oben in seiner Antwort erwähnt hat

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
quelle
1
Upvoted, da dies keine doppelte Antwort oder Plagiat ist, sondern ein gutes Beispiel dafür ist, dass andere Antworten fehlten, während das, was zuvor gepostet wurde, anerkannt wird.
Panzercrisis
Upvote ist für eine klare Antwort. Das gleiche wurde zwar von anderen geschrieben, aber das macht es jedem klar, der feststeckt.
NishantM
15

Aus irgendeinem Grund wurde Candides Antwort nicht erstellt. Es war jedoch hilfreich, da es mich dazu brachte, dies zu finden, was perfekt funktionierte:

System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
{
   //your code here...
}));
Sarah
quelle
Möglicherweise haben Sie nicht aus der Klasse des Formulars angerufen. Entweder können Sie einen Verweis auf das Fenster abrufen oder wahrscheinlich das verwenden, was Sie vorgeschlagen haben.
Simone
4
Wenn es für Sie funktioniert hat, war es nicht notwendig, es überhaupt zu verwenden. System.Windows.Threading.Dispatcher.CurrentDispatcherist der Dispatcher für den aktuellen Thread . Das heißt, wenn Sie sich in einem Hintergrund-Thread befinden, ist dies nicht der Dispatcher des UI-Threads. Verwenden Sie, um auf den Dispatcher des UI-Threads zuzugreifen System.Windows.Application.Current.Dispatcher.
13

Sie müssen in der Benutzeroberfläche aktualisieren, also verwenden

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

Das funktioniert bei mir.

new Thread(() =>
        {

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

          //Your Code here.

        }, null);
        }).Start();
nPcomp
quelle
3

Ich fand auch, dass dies System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()nicht immer der Dispatcher der Zielkontrolle ist, so wie dotNet in seiner Antwort schrieb. Ich hatte keinen Zugriff auf den eigenen Dispatcher der Steuerung, also habe ich verwendet Application.Current.Dispatcherund das Problem gelöst.

Paulus Limma
quelle
2

Das Problem ist, dass Sie GetGridDatavon einem Hintergrund-Thread aus aufrufen . Diese Methode greift auf mehrere WPF-Steuerelemente zu, die an den Hauptthread gebunden sind. Jeder Versuch, über einen Hintergrund-Thread auf sie zuzugreifen, führt zu diesem Fehler.

Um zum richtigen Thread zurückzukehren, sollten Sie verwenden SynchronizationContext.Current.Post. In diesem speziellen Fall scheint der Großteil der Arbeit, die Sie ausführen, auf der Benutzeroberfläche zu basieren. Daher würden Sie einen Hintergrund-Thread erstellen, um sofort zum UI-Thread zurückzukehren und einige Arbeiten auszuführen. Sie müssen Ihren Code ein wenig umgestalten, damit er die teure Arbeit am Hintergrund-Thread erledigen und anschließend die neuen Daten an den UI-Thread senden kann

JaredPar
quelle
2

Wie hier erwähnt , Dispatcher.Invokekönnte die Benutzeroberfläche einfrieren. Sollte Dispatcher.BeginInvokestattdessen verwenden.

Hier ist eine praktische Erweiterungsklasse, um das Überprüfen und Aufrufen des Dispatcher-Aufrufs zu vereinfachen.

Beispielnutzung: (Aufruf aus dem WPF-Fenster)

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

Erweiterungsklasse:

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
quelle
0

Eine andere Lösung besteht darin, sicherzustellen, dass Ihre Steuerelemente im UI-Thread erstellt werden, nicht beispielsweise von einem Hintergrund-Worker-Thread.

FindOutIslamNow
quelle
0

Ich habe immer wieder den Fehler erhalten, als ich meiner WPF-Anwendung kaskadierende Comboboxen hinzugefügt und den Fehler mithilfe dieser API behoben habe:

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

    }

Weitere Informationen finden Sie unter https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Windows.Data.BindingOperations.EnableCollectionSynchronization);k(TargetFrameworkMoniker-.NETFramework,Vers % 3Dv4.7); k (DevLang-csharp) & rd = true

user8128167
quelle