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

H.B*_*.B. 18 wpf multithreading

在WPF中使用多个线程时可以获得的常见异常是:

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

有什么方法可以妥善处理这个问题?

H.B*_*.B. 39

根据具体情况,有多种选择:

从另一个线程访问控件

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

  • 数据绑定:

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

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

  • 调度:

    您可以发送您的访问代码的线程拥有的对象,这可以通过调用来完成InvokeBeginInvokeDispatcher拥有该对象被访问(这越来越Dispatcher有可能在另一个线程).

    例如

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

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

    例如

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

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

特别案例:

  • BackgroundWorker

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

  • 计时器

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

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

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

例如,在后台加载图像.

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

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

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

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