c# - subprocesos - El subproceso que llama no puede acceder a este objeto porque un subproceso diferente lo posee
dispatcher c# (13)
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;
objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;
en obtener datos de cuadrícula arroja excepción
El subproceso que llama no puede acceder a este objeto porque otro subproceso lo posee.
¿Qué está mal aquí?
Además, otra solución es garantizar que sus controles se creen en el subproceso de la interfaz de usuario, no por ejemplo, en un subproceso de trabajo en segundo plano.
Como se mencionó here , Dispatcher.Invoke
podría congelar la interfaz de usuario. Debería usar Dispatcher.BeginInvoke
lugar.
Aquí hay una clase de extensión útil para simplificar la comprobación y la invocación del despachador de llamadas.
Ejemplo de uso: (llamar desde la ventana WPF)
this Dispatcher.InvokeIfRequired(new Action(() =>
{
logTextbox.AppendText(message);
logTextbox.ScrollToEnd();
}));
Clase de extension:
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();
}
}
}
El problema es que está llamando a GetGridData
desde un subproceso en segundo plano. Este método accede a varios controles WPF que están vinculados al hilo principal. Cualquier intento de acceder a ellos desde un subproceso en segundo plano llevará a este error.
Para volver al hilo correcto, 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 interfaz de usuario. Por lo tanto, estaría creando un hilo de fondo para volver 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 nuevos datos en el hilo de la interfaz de usuario después
Este es un problema común con las personas que comienzan. Cada vez que actualice sus elementos de UI desde un subproceso que no sea el subproceso principal, debe utilizar:
this.Dispatcher.Invoke(() =>
{
...// your code here.
});
También puede usar control.Dispatcher.CheckAccess()
para verificar si el hilo actual posee el control. Si es de su propiedad, su código parece normal. De lo contrario, utilice el patrón anterior.
Esto funciona para mi
new Thread(() =>
{
Thread.CurrentThread.IsBackground = false;
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate {
//Your Code here.
}, null);
}).Start();
Necesita actualizar en la interfaz de usuario, así que use
Dispatcher.BeginInvoke(new Action(() => {GetGridData(null, 0)}));
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);
Lo utilizo para actualizar el texto del botón " Procesando ... " y deshabilitarlo al realizar solicitudes de WebClient
.
Para agregar mis 2 centavos, la excepción puede ocurrir incluso si llama a su código a través de System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()
.
El punto es que tiene que llamar a Invoke()
del Dispatcher
del control al que intenta acceder , que en algunos casos puede no ser el mismo que System.Windows.Threading.Dispatcher.CurrentDispatcher
. Entonces, en lugar de eso, debes usar YourControl.Dispatcher.Invoke()
para estar seguro. Me golpeé la cabeza durante un par de horas antes de darme cuenta de esto.
Por alguna razón, la respuesta de Candide no se construyó. Sin embargo, fue útil, ya que me llevó a encontrar esto, que funcionó perfectamente:
System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
{
//your code here...
}));
Seguí recibiendo el error cuando agregué comboboxes 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,Version%3Dv4.7);k(DevLang-csharp)&rd=true
Si alguien intenta trabajar con BitmapSource
en WPF e hilos y tiene este mismo mensaje: simplemente llame al método Freeze()
antes de pasar un BitmapSource
como parámetro de hilo.
También encontré que System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()
no siempre es despachador del control de destino, tal como escribió dotNet en su respuesta. No tuve acceso al controlador de control, así que utilicé Application.Current.Dispatcher
y resolví el problema.
esto sucedió conmigo porque intenté access UI
componente access UI
en another 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 ui 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 );
}