【问题标题】: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;抛出异常

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

这里有什么问题?

【问题讨论】:

标签: c# wpf multithreading backgroundworker


【解决方案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 请求时禁用它。

【讨论】:

【解决方案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 articlethis 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() 不起作用

【讨论】:

【解决方案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/…我没有密码:)
                  • 啊,好的。感谢您的澄清!
                  • 感谢您指出这可能更清楚
                  猜你喜欢
                  • 1970-01-01
                  相关资源
                  最近更新 更多