不要使用 Dispatcher.Invoke,由于它可能在你的延迟初始化 Lazy 中致使死锁

原文: 不要使用 Dispatcher.Invoke,由于它可能在你的延迟初始化 Lazy 中致使死锁

版权声明:本做品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。欢迎转载、使用、从新发布,但务必保留文章署名吕毅(包含连接:https://walterlv.blog.csdn.net/),不得用于商业目的,基于本文修改后的做品务必以相同的许可发布。若有任何疑问,请与我联系(walter.lv@qq.com)。 https://blog.csdn.net/WPwalter/article/details/85222847

WPF 中为了 UI 的跨线程访问,提供了 Dispatcher 线程模型。其 Invoke 方法,不管在哪一个线程调用,均可以让传入的方法回到 UI 线程。html

然而,若是你在 Lazy 上下文中使用了 Invoke,那么当这个 Lazy<T> 跨线程并发时,极有可能致使死锁。本文将具体说说这个例子。安全


一段死锁的代码

请先看一段很是简单的 WPF 代码:markdown

private Lazy<Walterlv> _walterlvLazy = new Lazy<Walterlv>(() => new Walterlv());

private void OnLoaded(object sender, RoutedEventArgs e)
{
    Task.Run(() =>
    {
        // 在后台线程经过 Lazy 获取。
        var backgroundWalterlv = _walterlvLazy.Value;
    });

    // 等待一个时间,这样能够确保后台线程先访问到 Lazy,而且在完成以前,UI 线程也能访问到 Lazy。
    Thread.Sleep(50);

    // 在主线程经过 Lazy 获取。
    var walterlv = _walterlvLazy.Value;
}

而其中的 Walterlv 类的定义也是很是简单的:并发

class Walterlv
{
    public Walterlv()
    {
        // 等待一段时间,是为了给我么的测试程序一个准确的时机。
        Thread.Sleep(100);

        // Invoke 到主线程执行,里面什么都不作是为了证实毫不是里面代码带来的影响。
        Application.Current.Dispatcher.Invoke(() =>
        {
        });
    }
}

这里的 Application.Current.Dispatcher 并不必定必须是 Application.Current,只要是两个不一样线程拿到的 Dispatcher 的实例是同一个,就会死锁。异步

此死锁的触发条件

  1. Lazy<T> 的线程安全参数设置为默认的,也就是 LazyThreadSafetyMode.ExecutionAndPublication
  2. 后台线程和主 UI 线程并发访问这个 Lazy<T>,且后台线程先于主 UI 线程访问这个 Lazy<T>
  3. Lazy<T> 内部的代码包含主线程的 Invoke

此死锁的缘由

  1. 后台线程访问到 Lazy,因而 Lazy 内部得到同步锁;
  2. 主 UI 线程访问到 Lazy,因而主 UI 线程等待同步锁完成,并进入阻塞状态(以致于不能处理消息循环);
  3. 后台线程的初始化调用到 Invoke 须要到 UI 线程完成指定的任务后才会返回,但 UI 线程此时阻塞不能处理消息循环,以致于没法完成 Invoke 内的任务;

因而,后台线程在等待 UI 线程处理消息以便让 Invoke 完成,而主 UI 线程因为进入 Lazy 的等待,因而不能完成 Invoke 中的任务;因而发生死锁。async

此死锁的解决方法

Invoke 改成 InvokeAsync 便能解锁。post

这么作能解决的缘由是:后台线程可以及时返回,这样 UI 线程便可以继续执行,包括执行 InvokeAsync 中传入的任务。测试

实际上,以上多是最好的解决办法了。由于:ui

  1. 咱们使用 Lazy 而且设置线程安全,必定是由于这个初始化过程会被多个线程访问;
  2. 咱们会在 Lazy 的初始化代码中使用回到主线程的 Invoke,也是由于咱们预料到这份初始化代码可能在后台线程执行。

因此,这段初始化代码既然不可避免地会并发,那么就应该阻止并发形成的死锁问题。也就是不要使用 Invoke 而是改用 InvokeAsyncatom

若是须要使用 Invoke 的返回值,那么改成 InvokeAsync 以后,可使用 await 异步等待返回值。

更多死锁问题

死锁问题:

解决方法:

相关文章
相关标签/搜索