2012-03-16 67 views
219

Mi código es la siguienteEl subproceso de llamada no puede acceder a este objeto porque un subproceso diferente es el dueño

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

El paso objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null; en los datos de cuadrícula get lanza excepción

El subproceso de llamada no puede acceder a este objeto porque lo posee un subproceso diferente.

¿Qué pasa aquí?

+4

posible duplicado de http://stackoverflow.com/questions/2728896/the-calling-thread-cannot-access-this-object-because-a-different-thread-owns-it, http: // stackoverflow. com/questions/3146942/the-calling-thread-can-no-access-this-object-because-a-different-thread-owns-it, http://stackoverflow.com/questions/7684206/threading-issue-the- calling-thread-can-no-access-this-object-because-a-differen, http://stackoverflow.com/questions/8950347/the-calling-thread-cannot-access-this-object-because-a-different- thread-owns-it –

Respuesta

466

Este es un problema común con las personas que comienzan. Cada vez que actualice sus elementos de interfaz de usuario desde un subproceso distinto del hilo principal, es necesario utilizar:

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

También puede utilizar control.Dispatcher.CheckAccess() para comprobar si el hilo actual posee el control. Si es el propietario, tu código se ve como normal. De lo contrario, use el patrón de arriba.

+3

Tengo el mismo problema que OP; Mi problema ahora es que el evento causa ahora un desbordamiento de la pila. : \ – Malavos

+1

Volví a mi viejo proyecto y resolví esto. Además, había olvidado hacer +1 en esto. ¡Este método funciona bastante bien! Mejora el tiempo de carga de mi aplicación en 10 segundos o incluso más, solo usando hilos para cargar nuestros recursos localizados. ¡Aclamaciones! – Malavos

+3

Si no me equivoco, ni siquiera puede leer un objeto UI de un hilo no propietario; me sorprendió un poco – Elliot

2

El problema es que llama al GetGridData desde una cadena de fondo. Este método tiene acceso a varios controles WPF que están vinculados al hilo principal. Cualquier intento de acceder a ellos desde un hilo de fondo dará lugar a este error. Para obtener el hilo correcto, debe usar SynchronizationContext.Current.Post. Sin embargo, en este caso particular, parece que la mayoría del trabajo que está haciendo se basa en la interfaz de usuario. Por lo tanto, estaría creando un hilo de fondo solo para regresar inmediatamente al hilo de la interfaz de usuario y hacer algún trabajo. Necesita refactorizar su código un poco para que pueda hacer el trabajo costoso en el hilo de fondo y luego publicar los datos nuevos en el hilo de la interfaz de usuario después

11

tiene que actualizar a la interfaz de usuario, así que utilice

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

Otro buen uso para Dispatcher.Invoke es para actualizar inmediatamente la interfaz de usuario en una función que realiza otras tareas:

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

utilizo esto para actualizar el texto del botón a "Procesando ..." y deshabilitarlo al hacer solicitudes WebClient.

+4

Esta respuesta se está discutiendo en 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

Esto detuvo mi control de obtener datos de internet? –

10

Por alguna razón la respuesta de Candide no se construyó. Fue muy útil, sin embargo, ya que me llevó a encontrar este, que funcionó a la perfección:

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

Es posible que no hayas llamado desde la clase del formulario. O puede obtener una referencia a la Ventana, o puede usar lo que sugirió. – Simone

+1

Si le funcionó, no fue necesario usarlo en primer lugar. 'System.Windows.Threading.Dispatcher.CurrentDispatcher' es * el despachador para el hilo actual *. Eso significa que si tienes un hilo de fondo, ** ** no será el despachador del hilo de la interfaz de usuario. Para acceder al despachador del subproceso de interfaz de usuario, use 'System.Windows.Application.Current.Dispatcher'. – Will

21

Para añadir mis 2 centavos, a excepción puede ocurrir incluso si usted llama a un código a través System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke(). El punto es que debe llamar al Invoke() del Dispatcher del control que está intentando acceder al, que en algunos casos puede no ser el mismo que System.Windows.Threading.Dispatcher.CurrentDispatcher. Por lo tanto, en su lugar, debe usar YourControl.Dispatcher.Invoke() para estar seguro. Me estaba golpeando la cabeza por un par de horas antes de darme cuenta de esto.

+0

Naaah ... Si esto es cierto, no puede usar MVVM donde simplemente no tiene conocimiento del despachador del control. Obviamente, eso rompería miles de aplicaciones en todo el mundo. Por favor eloborate sobre esto. ¿Tiene un proyecto de muestra que demuestra este problema? Fuente en MSDN? Para agregar algunos centavos más, diría que 'BeginInvoke' se usará cuando' Invoke' parezca fallar (generalmente debido a algunas rarezas de 'enfoque/diseño/rendering' de WPF). – l33t

+2

@ l33t: WPF admite varios subprocesos de UI en una aplicación, cada uno de los cuales tendrá su propio 'Dispatcher'. En esos casos (que son ciertamente raros), llamar a 'Control.Dispatcher' es el enfoque seguro. Como referencia, puede ver [este artículo] (https://eprystupa.wordpress.com/2008/07/28/running-wpf-application-with-multiple-ui-threads/) así como también [esta publicación de SO] (http: // stackoverflow.com/questions/4620818/wpf-threading-dispatcher-static-vs-dispatcher-on-a-control) (particularmente la respuesta de Squidward). – dotNET

+0

Curiosamente, me enfrentaba a esta misma excepción cuando busqué en Google y me conecté a esta página y, como la mayoría de nosotros, probamos la respuesta más votada, que no solucionó mi problema en ese momento. Luego descubrí este motivo y lo publiqué aquí para desarrolladores pares. – dotNET

22

Si alguien intenta trabajar con BitmapSource en WPF y subprocesos y tiene el mismo mensaje: simplemente llame primero al método .Freeze() antes de pasar un BitmapSource como un parámetro de subproceso.

+7

Ah, nada como un buen truco vago y misterioso para resolver algo que nadie entiende. – Edwin

+0

Me gustaría más información sobre por qué funciona esto y cómo podría haberlo descubierto yo mismo. –

+0

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

14

esto sucedió conmigo porque intenté access UI componente en another thread insted of UI thread

como esto

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

para resolver este problema, ajuste cualquier llamada ui dentro 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, porque esto es *** no *** una respuesta duplicada o plagio, pero en su lugar, proporciona un buen ejemplo de que faltaron otras respuestas, mientras que da crédito por lo que se publicó anteriormente. – Panzercrisis

+0

Upvote es para una respuesta clara. Aunque lo mismo fue escrito por otros, pero esto deja en claro para cualquiera que esté estancado. – NishantM

0

Además, otro La solución es garantizar que sus controles se creen en el hilo de la interfaz de usuario, no por un hilo de trabajo en segundo plano, por ejemplo.

1

También encontré que System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke() no siempre es el despachador de control objetivo, tal como dotNet escribió en su respuesta. No tuve acceso al despachador del control, así que usé Application.Current.Dispatcher y resolvió el problema.

Cuestiones relacionadas