2012-03-16 6 views
219

Il mio codice è il seguenteIl thread chiamante non può accedere a questo oggetto perché un thread diverso lo possiede

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 passo objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null; nei dati griglia get getta eccezione

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

Cosa c'è che non va qui?

+4

possibile duplicato di http://stackoverflow.com/questions/2728896/the-calling-thread-cannot-access-this-object-because-a-different-thread-owns-it, http: // stackoverflow. it/questions/3146942/the-calling-thread-non può accedere-this-object-because-a-different-thread-owns-it, http://stackoverflow.com/questions/7684206/threading-issue-the- calling-thread-can-access-this-object-because-a-differen, http://stackoverflow.com/questions/8950347/the-calling-thread-cannot-access-this-object-because-a-different- thread-owns-it –

risposta

466

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

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

È inoltre possibile utilizzare 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 provoca ora uno stack overflow. : \ – Malavos

+1

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

+3

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

2

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

Per tornare alla discussione corretta è necessario utilizzare SynchronizationContext.Current.Post. Tuttavia in questo caso particolare sembra che la maggior parte del lavoro che stai facendo 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 riordinare il codice un po 'in modo che possa eseguire il costoso lavoro sul thread in background e quindi postare i nuovi dati sul thread dell'interfaccia utente successivamente

11

è necessario aggiornare all'interfaccia utente, quindi utilizzare

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

Un altro buon uso per Dispatcher.Invoke è per immediatamente aggiornare l'interfaccia utente in una funzione che svolge altri compiti:

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

io uso per aggiornare il testo del pulsante su "Elaborazione in corso ..." e disabilitarlo durante le richieste WebClient.

+4

Questa risposta è in discussione su Meta. https://meta.stackoverflow.com/questions/361844/what-to-do-about-answers-which-are-highly-upvoted-but-dont-technically-answer-t?cb=1 – JDB

+0

Questo ha fermato il mio controllo da ottenere dati da internet? –

10

Per qualche motivo la risposta di Candide non è stata costruita. E 'stato utile, anche se, come mi ha portato a trovare questo, che ha funzionato perfettamente:

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

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

+1

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 ** il dispatcher del thread dell'interfaccia utente. Per accedere al dispatcher del thread dell'interfaccia utente, utilizzare 'System.Windows.Application.Current.Dispatcher'. – Will

21

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

+0

Naaah ... Se ciò è vero, non è possibile utilizzare MVVM in cui semplicemente non si è a conoscenza del dispatcher del controllo. Ovviamente, ciò spezzerebbe migliaia di applicazioni in tutto il mondo. Per favore, elobora su questo. Avete un progetto di esempio che mostri questo problema? Fonte su MSDN? Per aggiungere altri centesimi, direi che 'BeginInvoke' deve essere usato quando' Invoke' sembra fallire (di solito a causa di alcune focalizzazione/layout/rendering di 'WPF'). – l33t

+2

@ 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] (https://eprystupa.wordpress.com/2008/07/28/running-wpf-application-with-multiple-ui-threads/) così come [questo post SO] (http: // StackOverflow.it/questions/4620818/wpf-threading-dispatcher-static-vs-dispatcher-on-a-control) (in particolare la risposta di Squidward). – dotNET

+0

È interessante notare che stavo affrontando questa eccezione quando ho cercato su Google e sono arrivato su questa pagina e come la maggior parte di noi, ha provato la risposta più votata, che non ha risolto il mio problema. Poi ho scoperto questo motivo e l'ho postato qui per gli sviluppatori peer. – dotNET

22

Se qualcuno tenta di lavorare con BitmapSource in WPF e thread e ha lo stesso messaggio: basta chiamare il metodo .Freeze() prima di passare a BitmapSource come parametro thread.

+7

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

+0

Mi piacerebbe avere più informazioni sul perché questo funziona e su come avrei potuto capirlo da solo. –

+0

@XavierShay https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/freezable-objects-overview – juFo

14

Questo è accaduto con me perché ho cercato di access UI componente another 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, avvolgere ogni chiamata ui all'interno what Candide mentioned above in his answer

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

Upvoted, perché questo non è *** una risposta duplicata o plagio, ma fornisce invece un buon esempio che mancano altre risposte, dando credito a ciò che è stato pubblicato in precedenza. – Panzercrisis

+0

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

0

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

1

Ho anche trovato che System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke() non è sempre il dispatcher del controllo del target, proprio come dotNet ha scritto nella sua risposta. Non ho avuto accesso al dispatcher del controllo, quindi ho utilizzato Application.Current.Dispatcher e ho risolto il problema.