【问题标题】:How to deal with cross-thread access exceptions?如何处理跨线程访问异常?
【发布时间】:2012-08-09 01:35:19
【问题描述】:

在 WPF 中使用多个线程时可能遇到的一个常见异常是:

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

有哪些方法可以正确处理这个问题?

【问题讨论】:

    标签: wpf multithreading


    【解决方案1】:

    根据情况有多种选择:

    从另一个线程访问控件

    例如使用进度信息更新 TextBlock。

    • Data Binding:

      在这种情况下,您可以做的最简单的事情是避免与控件直接交互。您可以将要访问或修改的属性bind 设置为类implements INotifyPropertyChanged 的对象,然后在该对象上设置属性。该框架将为您处理其余部分。 (一般来说,您很少需要直接与 UI 元素交互,您几乎总是可以绑定相应的属性并使用绑定源来代替;可能需要直接控制访问的一种情况是控件创作。)

      在某些情况下,仅数据绑定是不够的,例如当尝试修改绑定 ObservableCollection<T> 时,为此您需要...

    • Dispatching:

      您可以将访问代码分派给拥有该对象的线程,这可以通过在拥有被访问对象的Dispatcher 上调用InvokeBeginInvoke 来完成(在另一个线程上可以得到这个Dispatcher )。

      例如

      new Thread(ThisThreadStart).Start();
      
      void ThisThreadStart()
      {
          textBlock.Dispatcher.Invoke(new Action(() => textBlock.Text = "Test"));
      }
      

      如果不清楚方法在哪个线程上执行,您可以使用Dispatcher.CheckAccess 直接调度或执行操作。

      例如

      void Update()
      {
          Action action = () => myTextBlock.Text = "Test";
          var dispatcher = myTextBlock.Dispatcher;
          if (dispatcher.CheckAccess())
              action();
          else
              dispatcher.Invoke(action);
      }
      

      如果一个对象不是DispatcherObject,并且您仍然需要关联的Dispatcher,您可以在创建对象的线程中使用Dispatcher.CurrentDispatcher(在正在执行的方法中执行此操作线程不会对你有任何好处)。为方便起见,您通常在应用程序的主 UI 线程上创建对象;您可以使用Application.Current.Dispatcher 从任何地方获取该线程的Dispatcher

    特殊情况:

    • BackgroundWorker

      将任何控制访问移至ProgressChanged,因为它发生在创建实例的线程(当然应该是 UI 线程)上

    • 计时器

      在 WPF 中,为方便起见,您可以使用 DispatcherTimer,它会为您执行调度,因此在关联的调度程序上调用 Tick 中的任何代码。如果您可以将调度委托给数据绑定系统,那么您当然也可以使用普通计时器。

    您可以阅读更多关于Dispatcher 队列的工作原理和一般的 WPF 线程on MSDN

    访问在另一个线程上创建的对象

    例如在后台加载图像。

    如果有问题的对象不是Freezable,您通常应该避免在另一个线程上创建它或限制对创建线程的访问。如果是Freezable,则只需调用Freeze 即可使其可供其他线程访问。

    从另一个线程访问数据对象

    也就是说,正在更新其实例的类型是用户代码。如果抛出异常,这种情况可能是有人使用DependencyObject 作为数据类的基本类型造成的。

    这种情况与访问控件相同,可以应用相同的方法,但通常应首先避免。当然,这允许通过dependency properties 进行简单的属性更改通知,并且这些属性也可以绑定,但通常这不值得放弃线程独立性。您可以从INotifyPropertyChanged 获得更改通知,并且 WPF 中的绑定系统本质上是不对称的,总是有一个被绑定的属性(目标)和作为此绑定源的东西。通常 UI 是目标,数据是源,这意味着只有 UI 组件才需要依赖属性。

    【讨论】:

    • 那么这是一个愚蠢的问题。当 Dispatcher.CheckAccess() 抛出“调用线程无法访问此对象,因为不同的线程拥有它”时,你会怎么做?
    • @RogerWillcocks:我从来没有遇到过。给出一个完整的例子来重现这个......
    • Application.Current.Dispatcher.Invoke(new Action(() => textBox.Text = "Test" ));工作完美!谢谢!
    【解决方案2】:

    这将是几百行代码,我“想通了”。

    但总结是:

    App_OnStartup 生成后台线程

    在回调中,

    打电话

    Application.Current.MainWindow.Dispatcher.CheckAccess() - 获取异常 Application.Current.Dispatcher.CheckAccess() 没有

    【讨论】:

    • 问题是步骤Current.MainWindow,而不是MainWindow.Dispatcher,所以是的,这不是我的建议......
    • 如我所见,但鉴于 MainWindow 和 Dispatcher 不为空,您会期望一个旨在告诉您跨线程访问是否安全的方法不会引发跨线程异常
    【解决方案3】:

    我有一个 udp 侦听器对象,它通过事件进行通信,其中方法/回调在我的 mainWindow wpf .cs 文件中被 +='ed。

    使用参数调用事件处理函数,其中一个是我希望在 mainWindow.cs 的列表框中显示的消息

    使用 H.B. 在此线程中的信息。更多; 我使用以下代码在我的事件处理程序回调中添加、测试和处理了 wpf 中的交叉线程,但我使用的是真实消息而不是硬编码消息:

    listBox1.Dispatcher.Invoke(new Action(() => listBox1.Items.Add("MessageHere")));
    

    更新:

    这样更好,因为您可以在匿名函数中放入更多内容。

     listBox1.Dispatcher.Invoke((Action)delegate 
     {
         listBox1.Items.Add(e.ReaderMessage); 
     });
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-09-22
      • 1970-01-01
      • 1970-01-01
      • 2014-05-23
      • 2016-01-17
      • 2011-11-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多