当前位置: 首页 > news >正文

出让执行权:Task.Yield, Dispathcer.Yield

Yield 这个词很有意思,叫做“屈服”“放弃”“让步”,字面意义上是让出当前任务的执行权,转而让其他任务可以插入执行。TaskDispatcherThread 都有 Yield() 方法,看起来都可以让出当前任务的执行权。


如果在阅读中发现对本文涉及到的一些概念不太明白,可以阅读:

  • 深入了解 WPF Dispatcher 的工作原理(Invoke/InvokeAsync 部分)
  • 深入了解 WPF Dispatcher 的工作原理(PushFrame 部分)

Dispatcher.Yield

如果一个方法的实现比较耗时,为了不影响 UI 的响应,你会选择用什么方法呢?我之前介绍过的 Invoke 和 InvokeAsync 可以解决,将后续耗时的任务分割成一个个小的片段以低于用户输入和渲染的优先级执行。

Dispatcher.Yield 也可以,其行为更加类似于 Dispatcher.InvokeAsync(即采用 Dispatcher 调度的方式,事实上后面会说到其实就是调用了 InvokeAsync),而非 Dispatcher.Invoke(即采用 PushFrame 新开消息循环的方式)。

使用时需要 await

foreach(var item in collection)
{
    DoWorkWhichWillTakeHalfASecond();
    await Dispatcher.Yield();
}

这样,这个 foreach 将在每遍历到一个集合项的时候中断一次,让 UI 能够响应用户的交互输入和渲染。

Yield 方法可以传入一个优先级参数,指示继续执行后续任务的优先级。默认是 DispatcherPriority.Background,低于用户输入 DispatcherPriority.Input、 UI 逻辑 DispatcherPriority.Loaded 和渲染 DispatcherPriority.Render

Dispatcher.Yield 是如何做到出让执行权的呢?

查看源码,发现 DispatcherYield 的返回值是 DispatcherPriorityAwaiter,而它的 OnCompleted 方法是这样的:

public void OnCompleted(Action continuation)
{
    if(_dispatcher == null)
        throw new InvalidOperationException(SR.Get(SRID.DispatcherPriorityAwaiterInvalid));
    _dispatcher.InvokeAsync(continuation, _priority);
}

所以,其实真的就是 InvokeAsync。如果希望了解为何是 OnCompleted 方法,可以阅读 【C#】【多线程】【05-使用C#6.0】08-自定义awaitable类型 - L.M。

需要注意

Dispatcher.YieldDispatcher 类型的静态方法,而不是像 InvokeAsync 一样是实例方法。不过 C# 有一个神奇的特性——静态方法和实例方法可以在同一上下文中调用,而不用担心产生歧义。

例如:

using System.Windows.Threading;

class Demo : DispatcherObject
{
    void Test()
    {
        // 调用静态方法 Yield。
        await Dispatcher.Yield();
        // 调用实例方法 InvokeAsync。
        await Dispatcher.InvokeAsync(() => { });
    }
}

注意需要引用命名空间 System.Windows.Threading

Task.Yield

拿前面 Dispatcher.Yield 的例子,我们换成 Task.Yield

foreach(var item in collection)
{
    DoWorkWhichWillTakeHalfASecond();
    await Task.Yield();
}

效果与 Dispatcher.Yield(DispatcherPriority.Normal) 是一样的。因为 Task 调度回到线程上下文靠的是 SynchronizationContext,WPF UI 线程的 SynchronizationContext 被设置为了 DispatcherSynchronizationContext,使用 Dispatcher 调度;而 DispatcherSynchronizationContext 构造时传入的优先级默认是 Normal,WPF 并没有特殊传入一个别的值,所以 WPF UI 线程上使用 Task.Yield() 出让执行权后,恢复时使用的是 Normal 优先级,相当于 Dispatcher.Yield(DispatcherPriority.Normal)。

希望了解 DispatcherSynchronizationContext 的区别可以阅读 c# - Difference between Synchronization Context and Dispatcher - Stack Overflow。

DispatcherSynchronizationContext 执行 await 后续任务的上下文代码:

/// <summary>
///     Asynchronously invoke the callback in the SynchronizationContext.
/// </summary>
public override void Post(SendOrPostCallback d, Object state)
{
    // Call BeginInvoke with the cached priority.  Note that BeginInvoke
    // preserves the behavior of passing exceptions to
    // Dispatcher.UnhandledException unlike InvokeAsync.  This is
    // desireable because there is no way to await the call to Post, so
    // exceptions are hard to observe.
    _dispatcher.BeginInvoke(_priority, d, state);
}

既然是 Normal 优先级,那么在 UI 线程上的效果自然不如 Dispatcher.Yield。但是,Task.Yield 适用于任何线程,因为 SynchronizationContext 本身是与 Dispatcher 无关的,适用于任何线程。这样,于如果一个 Task 内部的任务太耗时,用 Task.Yield 则可以做到将此任务分成很多个片段执行。


参考资料

  • https://stackoverflow.com/questions/23431595/task-yield-real-usages“>c# - Task.Yield - real usages? - Stack Overflow
  • Task.Yield Method (System.Threading.Tasks)
  • c# - Difference between Synchronization Context and Dispatcher - Stack Overflow

相关文章:

  • 如何防止后台线程抛出的异常让程序崩溃退出
  • CaptureMouse/CaptureStylus 可能会失败
  • 使用 ExceptionDispatchInfo 捕捉并重新抛出异常
  • 使用 Task.Wait()?立刻死锁(deadlock)
  • 如何实现一个可以用 await 异步等待的 Awaiter
  • WPF 同一窗口内的多线程 UI(VisualTarget)
  • WPF 和 UWP 中,不用设置 From 或 To,Storyboard 即拥有更灵活的动画控制
  • 从 “x is null 和 x == null” 的区别看 C# 7 模式匹配中常量和 null 的匹配
  • 使用不安全代码将 Bitmap 位图转为 WPF 的 ImageSource 以获得高性能和持续小的内存占用
  • WPF 跨应用程序域的 UI(Cross AppDomain UI)
  • 将 UWP 的有效像素(Effective Pixels)引入 WPF
  • 用动画的方式画出任意的路径(直线、曲线、折现)
  • 使 WPF 支持触摸板的横向滚动
  • NullReferenceException,就不应该存在!
  • 当我们使用 MVVM 模式时,我们究竟在每一层里做些什么?
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • ECMAScript入门(七)--Module语法
  • Git的一些常用操作
  • iOS | NSProxy
  • java2019面试题北京
  • JavaScript设计模式与开发实践系列之策略模式
  • js数组之filter
  • Material Design
  • Node + FFmpeg 实现Canvas动画导出视频
  • PHP 小技巧
  • php的插入排序,通过双层for循环
  • Spring Security中异常上抛机制及对于转型处理的一些感悟
  • SQLServer之索引简介
  • 前端面试题总结
  • 让你成为前端,后端或全栈开发程序员的进阶指南,一门学到老的技术
  • 通过npm或yarn自动生成vue组件
  • 微服务核心架构梳理
  • TPG领衔财团投资轻奢珠宝品牌APM Monaco
  • ​软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】​
  • ​水经微图Web1.5.0版即将上线
  • # 手柄编程_北通阿修罗3动手评:一款兼具功能、操控性的电竞手柄
  • (BFS)hdoj2377-Bus Pass
  • (紀錄)[ASP.NET MVC][jQuery]-2 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)...
  • (转)Linux NTP配置详解 (Network Time Protocol)
  • *** 2003
  • .bat批处理(三):变量声明、设置、拼接、截取
  • .net 4.0 A potentially dangerous Request.Form value was detected from the client 的解决方案
  • .net wcf memory gates checking failed
  • .Net Web窗口页属性
  • .NET 动态调用WebService + WSE + UsernameToken
  • .NET/C# 使用反射注册事件
  • .NetCore部署微服务(二)
  • .NET面试题(二)
  • .NET设计模式(2):单件模式(Singleton Pattern)
  • .NET中使用Protobuffer 实现序列化和反序列化
  • .pub是什么文件_Rust 模块和文件 - 「译」
  • @Resource和@Autowired的区别
  • @RestControllerAdvice异常统一处理类失效原因
  • @Service注解让spring找到你的Service bean
  • [100天算法】-目标和(day 79)