Вызывающий поток не может получить доступ к этому объекту, потому что ему принадлежит другой поток
Мой код ниже
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);
}
}
Шаг objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;
в получении данных сетки исключает
Вызывающий поток не может получить доступ к этому объекту, потому что другой нить владеет им.
Что здесь не так?
Ответы
Ответ 1
Это обычная проблема с началом работы с людьми. Всякий раз, когда вы обновляете элементы пользовательского интерфейса из потока, отличного от основного потока, вам необходимо использовать:
this.Dispatcher.Invoke(() =>
{
...// your code here.
});
Вы также можете использовать control.Dispatcher.CheckAccess()
, чтобы проверить, принадлежит ли текущий поток элементу управления. Если он действительно владеет, ваш код выглядит нормально. В противном случае используйте шаблон выше.
Ответ 2
Еще одно хорошее применение для Dispatcher.Invoke
- это немедленное обновление пользовательского интерфейса в функции, которая выполняет другие задачи:
// Force WPF to render UI changes immediately with this magic line of code...
Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle);
Я использую это, чтобы обновить текст кнопки до "Обработка..." и отключить ее при выполнении запросов WebClient
.
Ответ 3
Чтобы добавить мои 2 цента, исключение может произойти, даже если вы вызываете свой код через System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()
.
Дело в том, что вы должны вызывать Invoke()
из Dispatcher
элемента управления, к которому вы пытаетесь получить доступ, который в некоторых случаях может не совпадать с System.Windows.Threading.Dispatcher.CurrentDispatcher
. Поэтому вместо этого вы должны использовать YourControl.Dispatcher.Invoke()
для безопасности. Я несколько часов колотил головой, прежде чем понял это.
Обновить
Для будущих читателей, похоже, что это изменилось в более новых версиях .NET(4.0 и выше). Теперь вам больше не нужно беспокоиться о правильном диспетчере при обновлении свойств поддержки пользовательского интерфейса в вашей виртуальной машине. Механизм WPF будет маршалировать вызовы между потоками в правильном потоке пользовательского интерфейса. Подробнее смотрите здесь. Спасибо @aaronburro за информацию и ссылку. Вы также можете прочитать наш разговор ниже в комментариях.
Ответ 4
Если вы столкнулись с этой проблемой, и элементы управления пользовательского интерфейса были созданы в отдельном рабочем потоке при работе с BitmapSource
или ImageSource
в WPF, сначала вызовите метод Freeze()
прежде чем передавать BitmapSource
или ImageSource
в качестве параметра любому методу. Использование Application.Current.Dispatcher.Invoke()
не работает в таких случаях
Ответ 5
это произошло со мной, потому что я попытался access UI
компонента в another thread insted of UI thread
как это
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);
}
чтобы решить эту проблему, оберните любой вызов ui внутри о чем упоминал Кандид в своем ответе
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 );
}
Ответ 6
Вам нужно обновить пользовательский интерфейс, поэтому используйте
Dispatcher.BeginInvoke(new Action(() => {GetGridData(null, 0)}));
Ответ 7
По какой-то причине ответ Candide не строился. Это было полезно, однако, поскольку это заставило меня найти это, которое отлично работало:
System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() =>
{
//your code here...
}));
Ответ 8
Я также обнаружил, что System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()
не всегда является диспетчером целевого управления, точно так же, как dotNet пишет в своем ответе. У меня не было доступа к управлению собственным диспетчером, поэтому я использовал Application.Current.Dispatcher
, и он решил проблему.
Ответ 9
Проблема заключается в том, что вы вызываете GetGridData
из фонового потока. Этот метод обращается к нескольким элементам управления WPF, которые привязаны к основному потоку. Любая попытка доступа к ним из фонового потока приведет к этой ошибке.
Чтобы вернуться к правильной теме, вы должны использовать SynchronizationContext.Current.Post
. Однако в этом конкретном случае, похоже, большая часть работы, которую вы выполняете, основана на пользовательском интерфейсе. Следовательно, вы создадите фоновый поток, чтобы сразу вернуться к потоку пользовательского интерфейса и выполнить некоторую работу. Вам нужно немного реорганизовать свой код, чтобы он мог выполнять дорогостоящую работу в фоновом потоке, а затем публиковать новые данные в потоке пользовательского интерфейса.
Ответ 10
Как уже упоминалось здесь, Dispatcher.Invoke
может заморозить UI. Вместо этого следует использовать Dispatcher.BeginInvoke
.
Вот удобный класс расширений, который упрощает проверку и вызов диспетчера вызовов.
Пример использования: (вызов из окна WPF)
this Dispatcher.InvokeIfRequired(new Action(() =>
{
logTextbox.AppendText(message);
logTextbox.ScrollToEnd();
}));
Класс расширения:
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();
}
}
}
Ответ 11
Это работает для меня.
new Thread(() =>
{
Thread.CurrentThread.IsBackground = false;
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate {
//Your Code here.
}, null);
}).Start();
Ответ 12
Кроме того, другое решение гарантирует, что ваши элементы управления создаются в потоке пользовательского интерфейса, а не потоком рабочего потока, например.
Ответ 13
Я продолжал получать ошибку, когда добавлял каскадные comboboxes в мое приложение WPF и разрешал ошибку с помощью этого 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 );
}
Для получения дополнительной информации см. Https://msdn.microsoft.com/query/dev14.query?appId=Dev14IDEF1&l=EN-US&k=k(System.Windows.Data.BindingOperations.EnableCollectionSynchronization);k(TargetFrameworkMoniker-.NETFramework, Версия% 3Dv4.7); к (DevLang-CSharp) & й = истина
Ответ 14
(недостаточно репутации для добавления комментария)
VikramBose предлагает использовать BeginInvoke
, но не упоминает EndInvoke
. Хорошая практика заключается в том, что "каждый BeginInvoke
имеет соответствующий EndInvoke
", и, конечно же, должна быть какая-то защита от условий гонки (подумайте: что происходит с несколькими BeginInvoke
кода, но пока еще не закончили обработку?)
Легко забыть, и я видел эту ошибку (и, да, это ошибка) как в примерах MSDN, так и в опубликованных книгах по WinForms