调用线程无法访问此对象,因为不同的线程拥有它

Posted

技术标签:

【中文标题】调用线程无法访问此对象,因为不同的线程拥有它【英文标题】:The calling thread cannot access this object because a different thread owns it 【发布时间】:2022-01-13 15:48:50 【问题描述】:

我的代码如下

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;抛出异常

调用线程无法访问该对象,因为不同的 线程拥有它。

这里有什么问题?

【问题讨论】:

***.com/questions/2728896/…、***.com/questions/3146942/…、***.com/questions/7684206/…、***.com/questions/8950347/… 的可能重复项 【参考方案1】:

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

this.Dispatcher.Invoke(() =>

    ...// your code here.
);

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

【讨论】:

我和OP有同样的问题;我现在的问题是该事件现在导致堆栈溢出。 :\ 回到我的旧项目并解决了这个问题。另外,我忘了+1这个。这个方法效果很好!只需使用线程加载我们的本地化资源,它就可以将我的应用程序加载时间缩短 10 秒甚至更多。干杯! 如果我没记错的话,你甚至无法从非所有者线程中读取 UI 对象;让我有点吃惊。 Application.Current.Dispatcher.Invoke(MyMethod, DispatcherPriority.ContextIdle); 根据this answer获取调度程序(如果不在 UI 线程上) +1。哈!我将它用于一些 WPF 黑客,以保持事物解耦。我在静态上下文中,所以我不能使用this.Dispatcher.Invoke.... 代替... myControl.Dispatcher.Invoke :) 我需要返回一个对象,所以我做了myControlDispatcher.Invoke&lt;object&gt;(() =&gt; myControl.DataContext);【参考方案2】:

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

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

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

【讨论】:

这个答案正在 Meta 上讨论。 meta.***.com/questions/361844/… 这阻止了我的控件从互联网获取数据?【参考方案3】:

加上我的 2 美分,即使您通过 System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke() 调用代码,也会发生异常。 关键是您必须调用您尝试访问的控件Dispatcher中的Invoke(),在某些情况下可能与System.Windows.Threading.Dispatcher.CurrentDispatcher不同.因此,为了安全起见,您应该使用YourControl.Dispatcher.Invoke()。在我意识到这一点之前,我已经敲了几个小时的头。

更新

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

【讨论】:

@l33t:WPF 在一个应用程序中支持多个 UI 线程,每个线程都有自己的Dispatcher。在这些情况下(诚然很少见),调用Control.Dispatcher 是安全的方法。作为参考,您可以查看 this article 和 this SO post(尤其是 Squidward 的回答)。 有趣的是,当我用谷歌搜索并登陆此页面时,我正面临这个异常,并且像我们大多数人一样,尝试了最高投票的答案,但当时并没有解决我的问题。然后我发现了这个原因,并在这里发布给同行开发者。 @l33t,如果您正确使用了 MVVM,那么它应该不是问题。视图必须知道它正在使用什么 Dispatcher,而 ViewModel 和 Model 对控件一无所知,也不需要知道控件。 @aaronburro:问题是 VM 可能希望在备用线程上启动操作(例如任务、基于计时器的操作、并行查询),并且随着操作的进行,可能希望更新 UI(通过 RaisePropertyChanged等),这将反过来尝试从非 UI 线程访问 UI 控件,从而导致此异常。我不知道可以解决此问题的正确的 MVVM 方法 WPF 绑定引擎自动将属性更改事件编组到正确的 Dispatcher。这就是为什么 VM 不需要知道 Dispatcher 的原因;它所要做的只是引发属性更改事件。 WinForms 绑定是另一回事。【参考方案4】:

如果您在 WPF 中使用 BitmapSourceImageSource 时遇到此问题,并且 UI 控件是在 单独的工作线程 上创建的,请在传递 BitmapSource 之前先调用 Freeze() 方法或 ImageSource 作为任何方法的参数。在这种情况下使用Application.Current.Dispatcher.Invoke() 不起作用

【讨论】:

啊,没有什么比一个古老的模糊而神秘的技巧更能解决没人理解的事情了。 我很想了解更多关于它为什么有效以及我自己是如何解决这个问题的信息。 @XavierShay docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/…【参考方案5】:

这发生在我身上,因为我试图在another thread insted of UI thread 中使用access UI 组件

喜欢这个

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 调用封装在 what Candide mentioned above in his answer 中

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】:

您需要在 UI 线程上执行此操作。使用:

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

【讨论】:

【参考方案7】:

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

System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke((Action)(() =>

   //your code here...
));

【讨论】:

您可能没有从表单的类中调用。您可以获取对 Window 的引用,也可以使用您的建议。 如果它对你有用,那么一开始就没有必要使用它。 System.Windows.Threading.Dispatcher.CurrentDispatcher当前线程的调度程序。这意味着如果您在后台线程上,它不会成为 UI 线程的调度程序。要访问 UI 线程的调度程序,请使用 System.Windows.Application.Current.Dispatcher【参考方案8】:

这对我有用。

new Thread(() =>
        

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

          //Your Code here.

        , null);
        ).Start();

【讨论】:

【参考方案9】:

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

【讨论】:

【参考方案10】:

如here 所述,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】:

根据您的需要,肯定有不同的方法可以做到这一点。

我使用 UI 更新线程(不是主 UI 线程)的一种方法是让线程启动一个循环,其中整个逻辑处理循环被调用到 UI 线程上。

例子:

public SomeFunction()

    bool working = true;
    Thread t = new Thread(() =>
    
        // Don't put the working bool in here, otherwise it will 
        // belong to the new thread and not the main UI thread.
        while (working)
        
            Application.Current.Dispatcher.Invoke(() =>
            
                // Put your entire logic code in here.
                // All of this code will process on the main UI thread because
                //  of the Invoke.
                // By doing it this way, you don't have to worry about Invoking individual
                //  elements as they are needed.
            );
        
    );

这样,代码完全在主 UI 线程上执行。对于那些难以理解跨线程操作的业余程序员来说,这可能是一个专业人士。但是,它很容易成为更复杂的 UI 的缺点(尤其是在执行动画时)。实际上,这只是为了伪造一个更新 UI 的系统,然后返回以处理任何已触发的事件,而不是有效的跨线程操作。

【讨论】:

【参考方案12】:

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

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

【讨论】:

【参考方案13】:

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

【讨论】:

【参考方案14】:

当我将级联组合框添加到我的 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

【讨论】:

【参考方案15】:

有时可能是您创建的对象引发了异常,而不是我明显查看的目标。

在我的代码中:

xaml 文件:

<Grid Margin="0,0,0,0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
    <TextBlock x:Name="tbScreenLog" VerticalAlignment="Stretch" Background="Black" FontSize="12" Foreground="#FF919191" HorizontalAlignment="Stretch"/>
</Grid>

xaml.cs 文件:

System.Windows.Documents.Run rnLine = new System.Windows.Documents.Run(Message.Item2 + "\r\n");
rnLine.Foreground = LineAlternate ? Brushes.Green : Brushes.Orange;

Dispatcher.Invoke(()=> 
    tbScreenLog.Inlines.Add(rnLine);
);
LineAlternate = !LineAlternate;

我遇到了关于从不同线程访问对象的异常,但我是在 UI 线程上调用它的??

过了一会儿,我吓坏了,这不是关于 TextBlock 对象,而是关于我在调用之前创建的 Run 对象。

将代码更改为此解决了我的问题:

Dispatcher.Invoke(()=> 
    Run rnLine = new Run(Message.Item2 + "\r\n");
    rnLine.Foreground = LineAlternate ? Brushes.Green : Brushes.Orange;
    tbScreenLog.Inlines.Add(rnLine);
);
LineAlternate = !LineAlternate;

【讨论】:

Run 不是类或结构恕我直言的合适名称。它有什么作用?可以附上代码吗? @TheodorZoulias 我同意这对于自定义类来说不是一个非常明智的名称,尽管它完全合法。 Run 是 System.Windows.Documents.Run 一个标准的 WPF 类。 docs.microsoft.com/en-us/dotnet/api/…我没有密码:) 啊,好的。感谢您的澄清! 感谢您指出这可能更清楚

以上是关于调用线程无法访问此对象,因为不同的线程拥有它的主要内容,如果未能解决你的问题,请参考以下文章

调用线程无法访问此对象,因为不同的线程拥有它 - WPF [重复]

“调用线程无法访问此对象,因为不同的线程拥有它”从 WPF 中的不同线程更新 UI 控件时出现错误

调用线程无法访问此对象,因为另一个线程拥有它[重复]

wpf 调用线程无法访问此对象,因为另一个线程拥有该对象。

调用线程无法访问此对象 - Timer [重复]

Wpf中“由于其他线程拥有此对象,因此调用线程无法对其进行访问”