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

341

Mi código es el siguiente

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 obtener la cuadrícula de datos arroja una excepción

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

¿Qué pasa aquí?

Kuntady Nithesh
fuente

Respuestas:

698

Este es un problema común con las personas que comienzan. Cada vez que actualice sus elementos de la interfaz de usuario desde un hilo que no sea el hilo principal, debe usar:

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

También puede usar control.Dispatcher.CheckAccess()para verificar si el hilo actual posee el control. Si lo posee, su código se ve normal. De lo contrario, use el patrón anterior.

Sincero
fuente
3
Tengo el mismo problema que OP; Mi problema ahora es que el evento causa ahora un desbordamiento de pila. : \
Malavos
2
Volví a mi antiguo proyecto y resolví esto. Además, me había olvidado de 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, simplemente usando hilos para cargar nuestros recursos localizados. ¡Salud!
Malavos
44
Si no me equivoco, ni siquiera puede leer un objeto de interfaz de usuario de un subproceso que no sea propietario; Me sorprendió un poco.
Elliot
32
Application.Current.Dispatcher.Invoke(MyMethod, DispatcherPriority.ContextIdle);para obtener el despachador si no está en el hilo de la interfaz de usuario según esta respuesta
JumpingJezza
2
+1. ¡Decir ah! Utilicé esto para algunos hackers de WPF para mantener las cosas desacopladas. Estaba en un contexto estático, así que no podía usar this.Dispatcher.Invoke... en cambio ... myControl.Dispatcher.Invoke:) Necesitaba devolver un objeto, así que lo hice myControlDispatcher.Invoke<object>(() => myControl.DataContext);
C. Tewalt
52

Otro buen uso para Dispatcher.Invokees 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);

Lo uso para actualizar el texto del botón a " Procesando ... " y deshabilitarlo al hacer WebClientsolicitudes.

computerGuyCJ
fuente
44
Esta respuesta se está discutiendo en Meta. meta.stackoverflow.com/questions/361844/…
JDB todavía recuerda a Monica el
¿Esto detuvo mi control de obtener datos de internet?
Waseem Ahmad Naeem
41

Para agregar mis 2 centavos, la excepción puede ocurrir incluso si llama a su código System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke().
El punto es que usted tiene que llamar Invoke()de la Dispatcherdel control que estás intentando acceder , lo que en algunos casos puede no ser el mismo que System.Windows.Threading.Dispatcher.CurrentDispatcher. Por lo tanto, debería usar YourControl.Dispatcher.Invoke()para estar seguro. Estuve golpeándome la cabeza durante un par de horas antes de darme cuenta de esto.

Actualizar

Para futuros lectores, parece que esto ha cambiado en las versiones más recientes de .NET (4.0 y superior). Ahora ya no tiene que preocuparse por el despachador correcto al actualizar las propiedades de respaldo de la interfaz de usuario en su VM. El motor WPF organizará las llamadas de subprocesos cruzados en el subproceso de interfaz de usuario correcto. Ver más detalles aquí . Gracias a @aaronburro por la información y el enlace. También puede leer nuestra conversación a continuación en los comentarios.

punto net
fuente
44
@ l33t: WPF admite múltiples hilos de interfaz de usuario en una aplicación, cada uno de los cuales tendrá el suyo Dispatcher. En esos casos (que son ciertamente raros), llamar Control.Dispatcheres el enfoque seguro. Como referencia, puede ver este artículo , así como esta publicación SO (particularmente la respuesta de Calamardo).
dotNET
1
Curiosamente, estaba enfrentando esta misma excepción cuando busqué en Google y llegué a esta página y, como la mayoría de nosotros, probé la respuesta más votada, que no resolvió mi problema en ese momento. Luego descubrí este motivo y lo publiqué aquí para desarrolladores pares.
dotNET
1
@ l33t, si está utilizando MVVM correctamente, entonces no debería ser un problema. La vista necesariamente sabe qué Despachador está usando, mientras que los Modelos y Modelos de Vista no saben nada de controles y no necesitan saber de controles.
aaronburro
1
@aaronburro: El problema es que VM puede querer iniciar acciones en subprocesos alternativos (por ejemplo, tareas, acciones basadas en temporizador, consultas paralelas) y, a medida que avanza la operación, puede querer actualizar la interfaz de usuario (a través de RaisePropertyChanged, etc.), que a su vez intentará para acceder a un control de UI desde un subproceso que no sea de UI y, por lo tanto, dar como resultado esta excepción No conozco un enfoque MVVM correcto que resuelva este problema.
dotNET
1
El motor de enlace de WPF organiza automáticamente eventos de cambio de propiedad en el Dispatcher correcto. Esta es la razón por la cual VM no necesita saber del Despachador; todo lo que tiene que hacer es aumentar los eventos de cambio de propiedad. WinForms vinculante es una historia diferente.
aaronburro
34

Si encuentra este problema y los controles de la IU se crearon en un subproceso de trabajo independiente al trabajar con BitmapSourceo ImageSourceen WPF, llame Freeze()primero al método antes de pasar el BitmapSourceo ImageSourcecomo parámetro a cualquier método. Usar Application.Current.Dispatcher.Invoke()no funciona en tales casos

juFo
fuente
24
Ah, nada como un buen viejo truco vago y misterioso para resolver algo que nadie entiende.
Edwin
2
Me encantaría obtener más información sobre por qué funciona esto y cómo podría haberlo descubierto yo mismo.
Xavier Shay
25

esto sucedió conmigo porque intenté access UIcomponer enanother thread insted of UI thread

Me gusta 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, envuelva cualquier llamada de interfaz de usuario dentro de lo que Candide mencionó anteriormente en su respuesta

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
fuente
1
Votado, porque esta no es una respuesta duplicada o plagiada, sino que proporciona un buen ejemplo de que faltaban otras respuestas, al tiempo que da crédito a lo publicado anteriormente.
Panzercrisis
El voto a favor es para una respuesta clara. Aunque lo mismo fue escrito por otros, pero esto deja en claro para cualquiera que esté atrapado.
NishantM
15

Por alguna razón, la respuesta de Candide no se desarrolló. Sin embargo, fue útil, ya que me llevó a encontrar esto, que funcionó perfectamente:

System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
{
   //your code here...
}));
Sarah
fuente
Es posible que no haya llamado desde la clase del formulario. Puede tomar una referencia a la ventana o puede usar lo que sugirió.
Simone
44
Si funcionó para usted, no fue necesario usarlo en primer lugar. System.Windows.Threading.Dispatcher.CurrentDispatcheres el despachador para el hilo actual . Eso significa que si está en un subproceso en segundo plano, no será el despachador del subproceso de interfaz de usuario. Para acceder al despachador del hilo de la IU, use System.Windows.Application.Current.Dispatcher.
13

Necesita actualizar a la interfaz de usuario, así que use

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

Esto funciona para mi.

new Thread(() =>
        {

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

          //Your Code here.

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

También descubrí que System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()no siempre se despacha el control de destino, tal como escribió dotNet en su respuesta. No tenía acceso al propio despachador de control, así que lo usé Application.Current.Dispatchery resolvió el problema.

Paulus Limma
fuente
2

El problema es que está llamando GetGridDatadesde un hilo de fondo. Este método accede 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 volver al hilo correcto que debe usar SynchronizationContext.Current.Post. Sin embargo, en este caso particular, parece que la mayoría del trabajo que está haciendo está basado en la IU. Por lo tanto, crearía un subproceso en segundo plano solo para volver inmediatamente al subproceso de la interfaz de usuario y hacer algo de trabajo. Debe refactorizar un poco su código para que pueda hacer el trabajo costoso en el subproceso en segundo plano y luego publicar los nuevos datos en el subproceso de la interfaz de usuario

JaredPar
fuente
2

Como se mencionó aquí , Dispatcher.Invokepodría congelar la interfaz de usuario. Debería usar Dispatcher.BeginInvokeen su lugar.

Aquí hay una práctica clase de extensión para simplificar la invocación del despachador de cheques y llamadas.

Ejemplo de uso: (llamada desde la ventana de WPF)

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

Clase de extensión:

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

Además, otra solución es garantizar que sus controles se creen en un subproceso de interfaz de usuario, no por un subproceso de trabajo en segundo plano, por ejemplo.

FindOutIslamNow
fuente
0

Seguí recibiendo el error cuando agregué cuadros combinados en cascada a mi aplicación WPF y resolví el error usando esta 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 );

    }

Para obtener más información, consulte https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Windows.Data.BindingOperations.EnableCollectionSynchronization);k(TargetFrameworkMoniker-.NETFramework,VersFramework,VersFramework,Vers % 3Dv4.7); k (DevLang-csharp) & rd = true

usuario8128167
fuente