前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >不要使用 Dispatcher.Invoke,因为它可能在你的延迟初始化 Lazy 中导致死锁

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

作者头像
walterlv
发布2023-10-22 09:49:39
3570
发布2023-10-22 09:49:39
举报
文章被收录于专栏:walterlv - 吕毅的博客

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

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

一段死锁的代码

请先看一段非常简单的 WPF 代码:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

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 类的定义也是非常简单的:

1 2 3 4 5 6 7 8 9 10 11 12 13

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 中的任务;于是发生死锁。

此死锁的解决方法

Invoke 改为 InvokeAsync 便能解锁。

这么做能解决的原因是:后台线程能够及时返回,这样 UI 线程便能够继续执行,包括执行 InvokeAsync 中传入的任务。

实际上,以上可能是最好的解决办法了。因为:

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

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

如果需要使用 Invoke 的返回值,那么改为 InvokeAsync 之后,可以使用 await 异步等待返回值。

更多死锁问题

死锁问题:

解决方法:

本文会经常更新,请阅读原文: https://blog.walterlv.com/post/deadlock-of-invoke-in-lazy.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 吕毅 (包含链接: https://blog.walterlv.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 ([email protected])

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-12-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一段死锁的代码
  • 此死锁的触发条件
  • 此死锁的原因
  • 此死锁的解决方法
  • 更多死锁问题
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档