19

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

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

有哪些选项可以正确处理这个问题?

4

3 回答 3

46

根据情况有多种选择:

从另一个线程访问控件

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

  • 数据绑定

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

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

  • 调度

    您可以将访问代码分派给拥有该对象的线程,这可以通过调用Invoke或拥有正在访问的对象来完成(可以在另一个线程上获得它BeginInvoke)。DispatcherDispatcher

    例如

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

    如果对象不是 aDispatcherObject并且您仍然需要可在创建对象的线程Dispatcher中使用的关联对象(因此在线程执行的方法中执行此操作对您没有任何好处)。为方便起见,您通常在应用程序的主 UI 线程上创建对象;您可以使用.Dispatcher.CurrentDispatcher DispatcherApplication.Current.Dispatcher

特别案例:

  • BackgroundWorker

    将任何控制访问移至ProgressChanged创建实例的线程(当然应该是 UI 线程)上

  • 计时器

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

您可以在 MSDN 上阅读有关Dispatcher队列工作原理和 WPF 线程的更多信息。

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

例如在后台加载图像。

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

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

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

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

于 2012-08-12T16:36:58.493 回答
0

对于我“想出”的东西,那将是几百行代码。

但总结是:

App_OnStartup 生成后台线程

在回调中,

称呼

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

于 2013-12-07T05:30:25.280 回答
0

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

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

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

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

更新:

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

 listBox1.Dispatcher.Invoke((Action)delegate 
 {
     listBox1.Items.Add(e.ReaderMessage); 
 });
于 2017-03-22T17:13:36.107 回答