c# - Task.Yield - 实际用途?

标签 c# multithreading async-await .net-4.5 c#-5.0

我一直在阅读 Task.Yield , 作为一名 Javascript 开发人员,我可以说它的工作与 setTimeout(function (){...},0); 完全相同。在让主单线程处理其他东西方面又名:

"don't take all the power , release from time time - so others would have some too..."



在 js 中,它在长循环中特别有效。 (不要让浏览器卡住...)

但是我看到了这个例子 here :
public static async Task < int > FindSeriesSum(int i1)
{
    int sum = 0;
    for (int i = 0; i < i1; i++)
    {
        sum += i;
        if (i % 1000 == 0) ( after a bulk , release power to main thread)
            await Task.Yield();
    }

    return sum;
}

作为 JS 程序员,我可以理解他们在这里做了什么。

但是作为 C# 程序员,我问自己:为什么不为它打开一个任务?
 public static async Task < int > FindSeriesSum(int i1)
    {
         //do something....
         return await MyLongCalculationTask();
         //do something
    }

问题

使用 Js 我无法打开任务(是的,我知道我实际上可以使用网络 worker )。但是用 c# 我可以 .

如果是这样 - 为什么还要不时发布,而我可以发布呢?

编辑

添加引用:

来自 here :
enter image description here

来自 here (另一本电子书):

enter image description here

最佳答案

当你看到:

await Task.Yield();
你可以这样想:
await Task.Factory.StartNew( 
    () => {}, 
    CancellationToken.None, 
    TaskCreationOptions.None, 
    SynchronizationContext.Current != null?
        TaskScheduler.FromCurrentSynchronizationContext(): 
        TaskScheduler.Current);
所有这一切都是为了确保延续将在 future 异步发生。我所说的异步是指执行控制将返回给 async 的调用者。方法,并且继续回调不会发生在同一个堆栈帧上。
何时以及在哪个线程上发生完全取决于调用者线程的同步上下文。
对于 UI 线程 ,继续将发生在消息循环的某个 future 迭代中,由 Application.Run 运行。 ( WinForms ) 或 Dispatcher.Run ( WPF )。在内部,它归结为 Win32 PostMessage API,将自定义消息发布到 UI 线程的消息队列。 await当此消息被抽取和处理时,将调用延续回调。你完全无法控制这到底什么时候会发生。
此外,Windows 有自己的消息泵送优先级:INFO: Window Message Priorities .最相关的部分:

Under this scheme, prioritization can be considered tri-level. All posted messages are higher priority than user input messages because they reside in different queues. And all user input messages are higher priority than WM_PAINT and WM_TIMER messages.


所以,如果你使用 await Task.Yield()屈服于消息循环以试图保持 UI 响应,您实际上处于阻碍 UI 线程的消息循环的风险中。一些待处理的用户输入消息,以及 WM_PAINTWM_TIMER ,比发布的继续消息具有更低的优先级。因此,如果您这样做 await Task.Yield()在紧密循环中,您仍然可能会阻塞 UI。
这就是它与 JavaScript 的 setTimer 的不同之处。你在问题中提到的类比。 A setTimer在浏览器的消息泵处理完所有用户输入消息后,将调用回调。
所以,await Task.Yield()不适合在 UI 线程上做后台工作。事实上,您很少需要在 UI 线程上运行后台进程,但有时您会这样做,例如编辑器语法高亮、拼写检查等。在这种情况下,使用框架的空闲基础设施。
例如,使用 WPF 你可以做 await Dispatcher.Yield(DispatcherPriority.ApplicationIdle) :
async Task DoUIThreadWorkAsync(CancellationToken token)
{
    var i = 0;

    while (true)
    {
        token.ThrowIfCancellationRequested();

        await Dispatcher.Yield(DispatcherPriority.ApplicationIdle);

        // do the UI-related work item
        this.TextBlock.Text = "iteration " + i++;
    }
}
对于 WinForms,您可以使用 Application.Idle事件:
// await IdleYield();

public static Task IdleYield()
{
    var idleTcs = new TaskCompletionSource<bool>();
    // subscribe to Application.Idle
    EventHandler handler = null;
    handler = (s, e) =>
    {
        Application.Idle -= handler;
        idleTcs.SetResult(true);
    };
    Application.Idle += handler;
    return idleTcs.Task;
}
It is recommended对于在 UI 线程上运行的此类后台操作的每次迭代,您不要超过 50 毫秒。
对于非 UI 线程 没有同步上下文,await Task.Yield()只需将延续切换到随机池线程。不能保证它将成为与当前线程不同的线程,它只能保证是一个异步延续。如 ThreadPool正在挨饿,它可能会将继续安排到同一线程上。
在 ASP.NET , 做 await Task.Yield()除了 @StephenCleary's answer 中提到的解决方法之外,完全没有意义。 .否则,它只会通过冗余线程切换来损害 Web 应用程序的性能。
所以,是 await Task.Yield()有用? 海事组织,不多。它可以用作通过 SynchronizationContext.Post 运行延续的快捷方式或 ThreadPool.QueueUserWorkItem ,如果你真的需要对你的方法的一部分强加异步。
关于你引用的书 ,在我看来,那些使用 Task.Yield 的方法错了。我在上面解释了为什么它们对于 UI 线程是错误的。对于非 UI 池线程,除非您运行像 Stephen Toub's AsyncPump 这样的自定义任务泵,否则根本就没有“线程中要执行的其他任务”。 .
更新回答评论:

... how can it be asynchronouse operation and stay in the same thread ?..


作为一个简单的例子:WinForms 应用程序:
async void Form_Load(object s, object e) 
{ 
    await Task.Yield(); 
    MessageBox.Show("Async message!");
}
Form_Load将返回给调用者(触发 Load 事件的 WinFroms 框架代码),然后消息框将异步显示,在由 Application.Run() 运行的消息循环的某些 future 迭代中.继续回调与 WinFormsSynchronizationContext.Post 一起排队,它在内部向 UI 线程的消息循环发布私有(private) Windows 消息。当这个消息被抽取时,回调将被执行,仍然在同一个线程上。
在控制台应用程序中,您可以使用 AsyncPump 运行类似的序列化循环。上文提到的。

关于c# - Task.Yield - 实际用途?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23431595/

相关文章:

c# - 如何获取 Winforms 窗体标题栏高度的大小?

c# - 如何在Unity中防止 "public"?

c# - 我无法修改 app.config 文件

java - 关闭 ExecutorService

c# - HttpClient PostAsync 和 SendAsync 之间的区别

javascript - 迷失在 Node、Express 和谷歌分析 API 的回调 hell 中

c# - 从网络请求中获取 C# 中的 JSON 对象

multithreading - 是否应该使用像 Akka 这样的基础设施来包装阻塞调用(到关系数据库)?

multithreading - 线程加工 AX 2012

asp.net-mvc-4 - 使用异步操作运行同步代码