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

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

发布于 2017-10-14 09:18 更新于 2018-02-19 22:31

一个耗时的任务,可以通过 Task.Yield 或者 Dispatcher.Yield 来中断以便分割成多个小的任务片段执行。

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


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

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://walterlv.com/post/yield-in-task-dispatcher.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码匠的流水账

聊聊leaky bucket算法的实现

15310
来自专栏小文博客

蓝桥杯 C语言省赛 习题2 格子中输出

17340
来自专栏数值分析与有限元编程

可视化 | 一个三角形常应变单元后处理例子

昨天提到了应力云图,其实质是用不同的颜色填充等值线。有了结点的应力值,单元内任意一点的应力值是通过插值实现的。下面来看一个悬臂梁的综合后处理。 如图所示,一个悬...

28970
来自专栏AI科技大本营的专栏

入门 | 海量数据处理算法总结【超详解】

作者 | Angel_Kitty ➤1. Bloom Filter 【Bloom Filter】 Bloom Filter(BF)是一种空间效率很高的随机数据...

45590
来自专栏chenjx85的技术专栏

leetcode-201-数字范围按位与

给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。

45320
来自专栏ml

HDUOJ-------- Fibonacci again and again

   Fibonacci again and again Time Limit : 1000/1000ms (Java/Other)   Memory Limi...

359110
来自专栏灯塔大数据

每周学点大数据 | No.5算法的分析之图灵机

No.5期 算法的分析之图灵机 小可:那计算机科学有没有对易解和难解问题进行一个相对严格的界定呢? Mr. 王:有的,这里既然提到了多项式算法和易解难解问题,...

38580
来自专栏潇涧技术专栏

Python Algorithms - C2 The basics

本节主要介绍了三个内容:算法渐近运行时间的表示方法、六条算法性能评估的经验以及Python中树和图的实现方式。

11820
来自专栏前端菜鸟变老鸟

判断两个json是不是相等的

19730
来自专栏数据结构与算法

第二类斯特灵数学习笔记

设$S(n, m)$表示把$n$个不同的球放到$m$个相同的盒子里,且不允许盒子为空的方案数

11440

扫码关注云+社区

领取腾讯云代金券