如何处理跨线程访问异常?

19

在使用WPF的多线程时,常见的异常之一是:

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

如何正确处理这个问题?

3个回答

47

根据情况,有多种选择:

从另一个线程访问控件

例如更新TextBlock的进度信息。

  • Data Binding:

    In this case the easiest thing you can do is avoiding the direct interaction with the control. You can just bind the property you want to access or modify to an object whose class implements INotifyPropertyChanged and then set the property on that object instead. The framework will handle the rest for you. (In general you rarely should need to interact with UI-elements directly, you can almost always bind the respective properties and work with the binding source instead; one case where direct control access may be necessary is control authoring.)

    There are some cases where data binding alone is not enough, for example when trying to modify a bound ObservableCollection<T>, for this you need...

  • Dispatching:

    You can dispatch your accessing code to the thread owning the object, this can be done by calling Invoke or BeginInvoke on the Dispatcher owning the object being accessed (getting this Dispatcher is possible on another thread).

    e.g.

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

    If it is not clear on which thread a method is executed you can use Dispatcher.CheckAccess to either dispatch or execute an action directly.

    e.g.

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

    If an object is not a DispatcherObject and you still need the associated Dispatcher you can use Dispatcher.CurrentDispatcher in the thread creating the object (so doing this in the method being executed by a thread will not do you any good). For convenience as you usually create objects on the application's main UI thread; you can get that thread's Dispatcher from anywhere using Application.Current.Dispatcher.

特殊情况:

  • BackgroundWorker

    将任何控件访问移动到发生在创建实例的线程上(当然应该是UI线程)的ProgressChanged中。

  • 计时器

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

您可以在MSDN上了解有关 Dispatcher 队列如何工作以及WPF线程的更多信息。

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

例如,在后台加载图像。

如果所讨论的对象不是Freezable,通常应该避免在另一个线程上创建它或限制对创建线程的访问。如果它是Freezable,只需要调用Freeze使其可被其他线程访问。

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

也就是说,正在更新实例的类型是用户代码。如果抛出异常,则可能是由于某个人将DependencyObject用作数据类的基类型。

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

那么这里有一个愚蠢的问题。当Dispatcher.CheckAccess()抛出“调用线程无法访问此对象,因为不同的线程拥有它”时,你该怎么办? - Roger Willcocks
1
@RogerWillcocks:我从来没有遇到过这种情况。请提供一个完整的示例以重现此问题... - H.B.
Application.Current.Dispatcher.Invoke(new Action(() => textBox.Text = "测试" )); 完美地工作了!谢谢! - Jason Foglia

0

这将是几百行代码,为了我“弄清楚”的东西。

但总结如下:

App_OnStartup 生成一个后台线程

在回调函数中,

调用

Application.Current.MainWindow.Dispatcher.CheckAccess() - 抛出异常 Application.Current.Dispatcher.CheckAccess() 不会抛出异常


1
问题在于Current.MainWindow这一步,而不是MainWindow.Dispatcher,所以是的,我不建议这样做... - H.B.
正如我所发现的那样,但是由于MainWindow和Dispatcher不为null,你会期望一个旨在告诉你跨线程访问是否安全的方法不会抛出跨线程异常。 - Roger Willcocks

0

我有一个UDP监听器对象,通过事件进行通信,在我的mainWindow wpf .cs文件中添加方法/回调。

事件处理程序函数被调用时带有参数,其中一个是我想在mainWindow.cs中的listbox中显示的消息。

使用H.B.在此线程中提供的信息; 我已经添加、测试并处理了WPF中的crossthread,在我的事件处理程序回调中使用以下代码,但我使用的是真实的消息而不是硬编码的消息:

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

更新:

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接