C# 调用线程无法访问此对象,因为其他线程拥有它

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9732709/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-09 08:37:44  来源:igfitidea点击:

The calling thread cannot access this object because a different thread owns it

c#wpfmultithreadingbackgroundworker

提问by Kuntady Nithesh

My code is as below

我的代码如下

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

The step objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;in get grid data throws exception

objUDMCountryStandards.Country = txtSearchCountry.Text.Trim() != string.Empty ? txtSearchCountry.Text : null;获取网格数据的步骤抛出异常

The calling thread cannot access this object because a different thread owns it.

调用线程无法访问此对象,因为其他线程拥有它。

What's wrong here?

这里有什么问题?

采纳答案by Candide

This is a common problem with people getting started. Whenever you update your UI elements from a thread other than the main thread, you need to use:

这是人们入门时的常见问题。每当您从主线程以外的线程更新 UI 元素时,您需要使用:

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

You can also use control.Dispatcher.CheckAccess()to check whether the current thread owns the control. If it does own it, your code looks as normal. Otherwise, use above pattern.

您还可以使用control.Dispatcher.CheckAccess()来检查当前线程是否拥有该控件。如果它确实拥有它,您的代码看起来很正常。否则,使用上面的模式。

回答by JaredPar

The problem is that you are calling GetGridDatafrom a background thread. This method accesses several WPF controls which are bound to the main thread. Any attempt to access them from a background thread will lead to this error.

问题是您是GetGridData从后台线程调用的。此方法访问绑定到主线程的多个 WPF 控件。任何从后台线程访问它们的尝试都将导致此错误。

In order to get back to the correct thread you should use SynchronizationContext.Current.Post. However in this particular case it seems like the majority of the work you are doing is UI based. Hence you would be creating a background thread just to go immediately back to the UI thread and do some work. You need to refactor your code a bit so that it can do the expensive work on the background thread and then post the new data to the UI thread afterwards

为了回到正确的线程,您应该使用SynchronizationContext.Current.Post. 然而,在这种特殊情况下,您所做的大部分工作似乎都是基于 UI 的。因此,您将创建一个后台线程只是为了立即返回 UI 线程并做一些工作。您需要稍微重构一下代码,以便它可以在后台线程上完成昂贵的工作,然后将新数据发布到 UI 线程

回答by VikramBose

You need to Update in to the UI, So use

您需要更新到 UI,所以使用

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

回答by computerGuyCJ

Another good use for Dispatcher.Invokeis for immediately updating the UI in a function that performs other tasks:

另一个很好的用途Dispatcher.Invoke是在执行其他任务的函数中立即更新 UI:

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

I use this to update button text to "Processing..." and disable it while making WebClientrequests.

我使用它来将按钮文本更新为“正在处理...”并在发出WebClient请求时禁用它。

回答by Sarah

For some reason Candide's answer didn't build. It was helpful, though, as it led me to find this, which worked perfectly:

出于某种原因,老实人的回答没有成立。不过,这很有帮助,因为它让我找到了这个,它工作得很好:

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

回答by dotNET

To add my 2 cents, the exception can occur even if you call your code through System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke().
The point is that you have to call Invoke()of the Dispatcherof the control that you're trying to access, which in some cases may not be the same as System.Windows.Threading.Dispatcher.CurrentDispatcher. So instead you should use YourControl.Dispatcher.Invoke()to be safe. I was banging my head for a couple of hours before I realized this.

再加上我的 2 美分,即使您通过System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke().
问题的关键是,你必须调用Invoke()Dispatcher的的,你要访问的控制,在某些情况下可能不一样System.Windows.Threading.Dispatcher.CurrentDispatcher。因此,您应该使用YourControl.Dispatcher.Invoke()以确保安全。在我意识到这一点之前,我敲了几个小时的头。

Update

更新

For future readers, it looks like this has changed in the newer versions of .NET (4.0 and above). Now you no longer have to worry about the correct dispatcher when updating UI-backing properties in your VM. WPF engine will marshal cross-thread calls on the correct UI thread. See more details here. Thanks to @aaronburro for the info and link. You may also want to read our conversation below in comments.

对于未来的读者来说,这在 .NET 的较新版本(4.0 及更高版本)中似乎发生了变化。现在,在更新 VM 中的 UI 支持属性时,您不再需要担心正确的调度程序。WPF 引擎将在正确的 UI 线程上封送跨线程调用。在此处查看更多详细信息。感谢@aaronburro 提供的信息和链接。您可能还想在下面的评论中阅读我们的对话。

回答by juFo

If you encounter this problem and UI Controls were created on a separate workerthread when working with BitmapSourceor ImageSourcein WPF, call Freeze()method first before passing the BitmapSourceor ImageSourceas a parameter to any method. Using Application.Current.Dispatcher.Invoke()does not work in such instances

如果您在使用 WPF或在 WPF 中遇到此问题并且 UI 控件是在单独的工作线程上创建的,请先调用方法,然后再将或作为参数传递给任何方法。在这种情况下使用不起作用BitmapSourceImageSourceFreeze()BitmapSourceImageSourceApplication.Current.Dispatcher.Invoke()

回答by Basheer AL-MOMANI

this happened with me because I tried to access UIcomponent in another thread insted of UI thread

这事与我,因为我试图access UI成分another thread insted of UI thread

like this

像这样

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

to solve this problem, wrap any ui call inside what Candide mentioned above in his answer

要解决此问题,请将任何 ui 调用包含在Candide 在他的回答中提到的内容中

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

回答by FindOutIslamNow

Also, another solution is ensuring your controls are created in UI thread, not by a background worker thread for example.

此外,另一个解决方案是确保您的控件在 UI 线程中创建,而不是由后台工作线程创建。

回答by Paulus Limma

I also found that System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()is not always dispatcher of target control, just as dotNet wrote in his answer. I didn't had access to control's own dispatcher, so I used Application.Current.Dispatcherand it solved the problem.

我还发现它System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()并不总是目标控制的调度员,正如 dotNet 在他的回答中所写的那样。我没有访问控制自己的调度程序的权限,所以我使用了Application.Current.Dispatcher它并解决了问题。