Il thread chiamante non può accedere a questo oggetto perché lo possiede un thread diverso


342

Il mio codice è come di seguito

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

Il passaggio objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;a get data grid genera un'eccezione

Il thread chiamante non può accedere a questo oggetto perché lo possiede un thread diverso.

Cosa c'è che non va qui?


Risposte:


699

Questo è un problema comune con le persone che iniziano. Ogni volta che aggiorni gli elementi dell'interfaccia utente da un thread diverso dal thread principale, devi utilizzare:

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

Puoi anche usare control.Dispatcher.CheckAccess()per verificare se il thread corrente possiede il controllo. Se lo possiede, il tuo codice sembra normale. Altrimenti, usa il modello sopra.


3
Ho lo stesso problema di OP; Il mio problema ora è che l'evento causa ora un overflow dello stack. : \
Malavos

2
Sono tornato al mio vecchio progetto e ho risolto questo. Inoltre, avevo dimenticato di fare +1 su questo. Questo metodo funziona abbastanza bene! Migliora il tempo di caricamento della mia applicazione su 10 secondi o anche di più, semplicemente usando i thread per caricare le nostre risorse localizzate. Saluti!
Malavos

4
Se non sbaglio non puoi nemmeno leggere un oggetto UI da un thread non proprietario; mi ha sorpreso un po '.
Elliot

32
Application.Current.Dispatcher.Invoke(MyMethod, DispatcherPriority.ContextIdle);ottenere il dispatcher se non sul thread dell'interfaccia utente in base a questa risposta
JumpingJezza

2
+1. Ha! L'ho usato per alcuni hacker WPF per mantenere le cose disaccoppiate. Ero in un contesto statico, quindi non potevo usare this.Dispatcher.Invoke.... invece ... myControl.Dispatcher.Invoke:) Avevo bisogno di restituire un oggetto, così ho fatto myControlDispatcher.Invoke<object>(() => myControl.DataContext);
C. Tewalt,

53

Un altro buon uso di Dispatcher.Invokeè per aggiornare immediatamente l'interfaccia utente in una funzione che esegue altre attività:

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

Lo uso per aggiornare il testo del pulsante in " Elaborazione in corso ... " e disabilitarlo durante le WebClientrichieste.


4
Questa risposta è in discussione su Meta. meta.stackoverflow.com/questions/361844/…
JDB ricorda ancora Monica

Ciò ha impedito al mio controllo di ottenere dati da Internet?
Waseem Ahmad Naeem,

41

Per aggiungere i miei 2 centesimi, l'eccezione può verificarsi anche se si chiama il codice System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke().
Il punto è che si deve chiamare Invoke()la Dispatcherdel controllo che si sta tentando di accedere , che in alcuni casi potrebbe non essere lo stesso System.Windows.Threading.Dispatcher.CurrentDispatcher. Quindi invece dovresti usare YourControl.Dispatcher.Invoke()per essere al sicuro. Mi sono sbattuto la testa per un paio d'ore prima di rendermene conto.

Aggiornare

Per i lettori futuri, sembra che questo sia cambiato nelle versioni più recenti di .NET (4.0 e successive). Ora non devi più preoccuparti del dispatcher corretto quando aggiorni le proprietà di supporto dell'interfaccia utente nella tua macchina virtuale. Il motore WPF eseguirà il marshalling delle chiamate cross-thread sul thread UI corretto. Vedi maggiori dettagli qui . Grazie a @aaronburro per le informazioni e il link. Puoi anche leggere la nostra conversazione di seguito nei commenti.


4
@ l33t: WPF supporta più thread dell'interfaccia utente in un'applicazione, ognuno dei quali avrà il proprio Dispatcher. In quei casi (che sono certamente rari), chiamare Control.Dispatcherè l'approccio sicuro. Per riferimento puoi vedere questo articolo così come questo post SO (in particolare la risposta di Squidward).
dotNET,

1
È interessante notare che stavo affrontando questa stessa eccezione quando ho cercato su Google e sono arrivato su questa pagina e, come molti di noi, ho provato la risposta più votata, che non ha risolto il mio problema. Ho quindi scoperto questo motivo e l'ho pubblicato qui per sviluppatori peer.
dotNET,

1
@ l33t, se si utilizza MVVM correttamente, non dovrebbe essere un problema. La vista conosce necessariamente quale Dispatcher sta utilizzando, mentre ViewModels e Models non conoscono i controlli e non hanno bisogno di conoscere i controlli.
aaronburro,

1
@aaronburro: il problema è che la VM potrebbe voler avviare azioni su thread alternativi (ad esempio Attività, Azioni basate su timer, Query parallele) e, man mano che l'operazione procede, potrebbe voler aggiornare l'interfaccia utente (tramite RaisePropertyChanged ecc.), che a sua volta proverà per accedere a un controllo UI da thread non UI e quindi generare questa eccezione. Non conosco un approccio MVVM corretto che risolva questo problema.
dotNET,

1
Il motore di associazione WPF esegue automaticamente il marshalling degli eventi di modifica delle proprietà nel Dispatcher corretto. Questo è il motivo per cui la VM non ha bisogno di conoscere il Dispatcher; tutto ciò che deve fare è solo aumentare gli eventi con cambio di proprietà. L'associazione WinForms è una storia diversa.
aaronburro,

34

Se si riscontra questo problema e i controlli dell'interfaccia utente sono stati creati su un thread di lavoro separato quando si lavora con BitmapSourceo ImageSourcein WPF, chiamare il Freeze()metodo prima di passare il parametro BitmapSourceo ImageSourcecome parametro a qualsiasi metodo. L'uso Application.Current.Dispatcher.Invoke()non funziona in questi casi


24
Ah, niente come un buon vecchio trucco vago e misterioso per risolvere qualcosa che nessuno capisce.
Edwin

2
Mi piacerebbe avere maggiori informazioni sul perché questo funziona e su come avrei potuto capirlo da solo.
Xavier Shay,


25

questo è successo con me perché ho provato a fare il access UIcomponenteanother thread insted of UI thread

come questo

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

per risolvere questo problema, avvolgi qualsiasi chiamata all'interno di ciò che Candide ha menzionato sopra nella sua risposta

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
Eseguito l'upgrade, perché questa non è una risposta duplicata o plagio, ma fornisce invece un buon esempio della mancanza di altre risposte, dando credito a ciò che è stato pubblicato in precedenza.
Panzercrisis,

Il voto è per una risposta chiara. Anche se lo stesso è stato scritto da altri, ma questo lo rende chiaro a chiunque sia bloccato.
NishantM

15

Per qualche motivo la risposta di Candido non è stata costruita. È stato utile, tuttavia, poiché mi ha portato a trovare questo, che ha funzionato perfettamente:

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

È possibile che tu non abbia chiamato dalla classe del modulo. O puoi prendere un riferimento alla finestra, oppure puoi probabilmente usare ciò che hai suggerito.
Simone,

4
Se ha funzionato per te, non era necessario usarlo in primo luogo. System.Windows.Threading.Dispatcher.CurrentDispatcherè il dispatcher per il thread corrente . Ciò significa che se sei su un thread in background, non sarà il dispatcher del thread dell'interfaccia utente. Per accedere al dispatcher del thread dell'interfaccia utente, utilizzare System.Windows.Application.Current.Dispatcher.

13

Devi aggiornare l'interfaccia utente, quindi usa

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

4

Questo funziona per me.

new Thread(() =>
        {

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

          //Your Code here.

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

3

Ho anche scoperto che System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()non sempre è il dispatcher del controllo del bersaglio, proprio come ha scritto dotNet nella sua risposta. Non avevo accesso al dispatcher di controllo, quindi ho usato Application.Current.Dispatchere risolto il problema.


2

Il problema è che stai chiamando GetGridDatada un thread in background. Questo metodo accede a numerosi controlli WPF associati al thread principale. Qualsiasi tentativo di accedervi da un thread in background porterà a questo errore.

Per tornare al thread corretto dovresti usare SynchronizationContext.Current.Post. Tuttavia, in questo caso particolare, sembra che la maggior parte del lavoro svolto sia basato sull'interfaccia utente. Quindi dovresti creare un thread in background solo per tornare immediatamente al thread dell'interfaccia utente e fare un po 'di lavoro. È necessario riformattare un po 'il codice in modo che possa svolgere il costoso lavoro sul thread in background e quindi pubblicare successivamente i nuovi dati nel thread dell'interfaccia utente


2

Come menzionato qui , Dispatcher.Invokepotrebbe bloccare l'interfaccia utente. Dovrebbe usare Dispatcher.BeginInvokeinvece.

Ecco una comoda classe di estensione per semplificare il controllo e la chiamata dell'invocazione del dispatcher.

Esempio di utilizzo: (chiamata dalla finestra WPF)

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

Classe di estensione:

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

Inoltre, un'altra soluzione è garantire che i controlli vengano creati nel thread dell'interfaccia utente, non ad esempio da un thread di lavoro in background.


0

Ho continuato a ricevere l'errore quando ho aggiunto caselle combinate a cascata alla mia applicazione WPF e ho risolto l'errore utilizzando questa 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 );

    }

Per i dettagli, consultare 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) e RD = true

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.