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


341

我的代码如下

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;获取网格数据的步骤抛出异常

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

怎么了


Answers:


697

这是人们入门的普遍问题。每当从主线程以外的其他线程更新UI元素时,都需要使用:

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

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


3
我和OP有同样的问题;我现在的问题是该事件现在导致堆栈溢出。:\
Malavos 2014年

2
回到我的旧项目并解决了这个问题。另外,我忘记为此+1。这种方法效果很好!仅通过使用线程来加载本地化资源,它就可以将我的应用程序加载时间缩短10秒甚至更长。干杯!
马拉沃斯2014年

4
如果我没看错,您甚至无法从非所有者线程中读取UI对象。让我有些惊讶。
Elliot'3

32
Application.Current.Dispatcher.Invoke(MyMethod, DispatcherPriority.ContextIdle);按照此答案,
JumpingJezza

2
+1。哈!我将此用于一些WPF黑客,以使事情脱钩。我在静态环境中,所以我不能使用this.Dispatcher.Invoke....而是... myControl.Dispatcher.Invoke:)我需要返回一个对象,所以我这样做了myControlDispatcher.Invoke<object>(() => myControl.DataContext)
C. Tewalt '16

52

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

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

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



这阻止了我从互联网上获取数据的控制权?
Waseem Ahmad Naeem

41

要加2美分,即使您通过调用代码,也会发生例外情况System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke()
问题的关键是,你必须调用Invoke()Dispatcher的的,你要访问的控制,在某些情况下可能不一样System.Windows.Threading.Dispatcher.CurrentDispatcher。因此,相反,您应该使用YourControl.Dispatcher.Invoke()它是安全的。在意识到这一点之前,我敲了几个小时。

更新资料

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


4
@ l33t:WPF在一个应用程序中支持多个UI线程,每个线程都有自己的Dispatcher。在这种情况下(公认的很少),打电话Control.Dispatcher是安全的方法。作为参考,您可以看到本文以及此SO帖子(特别是Squidward的答案)。
dotNET

1
有趣的是,我在Google上搜索并登陆此页面时遇到了一个非常例外,就像我们大多数人一样,尝试了投票最高的答案,但当时这并没有解决我的问题。然后,我找出了这个原因,并将其发布在这里供同行开发人员使用。
dotNET

1
@ l33t,如果您正确使用了MVVM,则应该没有问题。视图必定知道它正在使用什么Dispatcher,而ViewModel和Models却不知道控件,也不需要知道控件。
aaronburro

1
@aaronburro:问题在于VM可能希望在备用线程上启动操作(例如,任务,基于计时器的操作,并行查询),并且随着操作的进行,可能想更新UI(通过RaisePropertyChanged等),这将依次尝试从非UI线程访问UI控件,从而导致此异常。我不知道可以解决此问题的正确MVVM方法
dotNET

1
WPF绑定引擎自动将属性更改事件封送给正确的Dispatcher。这就是为什么VM不需要知道Dispatcher的原因。它所要做的只是引发属性更改事件。WinForms绑定是另一回事。
aaronburro

34

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


24
啊,没有什么比解决以前没人知道的好的模糊而神秘的技巧更好了。
艾德温

2
我希望获得更多信息,以了解其工作原理以及如何自己解决问题。
Xavier Shay

@XavierShaydocs.microsoft.com/ zh
cn/

25

这事与我,因为我试图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调用包装在他的答案中上述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 );
}

1
赞成,因为这不是重复的答案或窃,但它提供了一个很好的例子,它缺乏其他答案,同时也赞扬了先前发布的内容。
Panzercrisis

Upvote是一个明确的答案。尽管相同的代码是由其他人编写的,但是对于任何被卡住的人来说,这都很清楚。
NishantM '17

15

由于某种原因,Candide的答案没有建立。但是,这很有帮助,因为它使我找到了这个,它非常有效:

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

您可能没有从表单的类中调用。您可以获取对Window的引用,也可以使用建议的内容。
西蒙妮(Simone)2015年

4
如果它对您有用,则无需首先使用它。System.Windows.Threading.Dispatcher.CurrentDispatcher当前线程的调度程序。这意味着,如果您使用的是后台线程,则不会成为UI线程的调度程序。要访问UI线程的调度程序,请使用System.Windows.Application.Current.Dispatcher

13

您需要更新到用户界面,因此请使用

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

4

这对我有用。

new Thread(() =>
        {

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

          //Your Code here.

        }, null);
        }).Start();

3

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


2

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

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


2

如前所述这里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();
        }
    }
}

0

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


0

当我向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,Version %3Dv4.7); k(DevLang-csharp)&rd = true

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.