Task.Yield - 真实用法?

Roy*_*mir 38 c# multithreading async-await c#-5.0 .net-4.5

我一直在阅读有关Task.Yield,而且作为一名JavaScript开发,我可以告诉大家,就是它的工作是 完全相同一样setTimeout(function (){...},0);在让主单线程处理又名其他的东西方面:

"不要把所有的力量从时间上释放出来 - 所以其他人也会有一些......"

在js中,它特别适用于长循环.(不要让浏览器冻结......)

但我在这里看到了这个例子:

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;
}
Run Code Online (Sandbox Code Playgroud)

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

但作为一名C#程序员,我问自己:为什么不为它开一个任务呢?

 public static async Task < int > FindSeriesSum(int i1)
    {
         //do something....
         return await MyLongCalculationTask();
         //do something
    }
Run Code Online (Sandbox Code Playgroud)

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

如果是这样 - 为什么甚至在我可以释放它的时候不时释放呢?

编辑

添加参考:

这里: 在此输入图像描述

这里(另一本电子书):

在此输入图像描述

nos*_*tio 60

当你看到:

await Task.Yield();
Run Code Online (Sandbox Code Playgroud)

你可以这样思考:

await Task.Factory.StartNew( 
    () => {}, 
    CancellationToken.None, 
    TaskCreationOptions.None, 
    SynchronizationContext.Current != null?
        TaskScheduler.FromCurrentSynchronizationContext(): 
        TaskScheduler.Current);
Run Code Online (Sandbox Code Playgroud)

所有这一切确保了延续将在未来异步发生.通过异步我的意思是执行控件将返回到async方法的调用者,并且延续回调不会发生在同一堆栈帧上.

确切地说,它将在什么线程上完全取决于调用者线程的同步上下文.

对于UI线程,延续将在消息循环的某个未来迭代中发生,由Application.Run(WinForms)或Dispatcher.Run(WPF)运行.在内部,它归结为Win32 PostMessageAPI,它将自定义消息发布到UI线程的消息队列.在await当这个消息被泵送和处理延续回调将被调用.关于何时会发生这种情况,你完全无法控制.

此外,Windows有自己的优先级来抽取消息:INFO:窗口消息优先级.最相关的部分:

在这种方案下,优先级可以被认为是三级的.所有发布的消息都优先于用户输入消息,因为它们位于不同的队列中.并且所有用户输入消息的优先级都高于WM_PAINT和WM_TIMER消息.

因此,如果您await Task.Yield()在尝试保持UI响应时使用消息循环,则实际上存在阻碍UI线程消息循环的风险.一些待处理的用户输入信息,以及WM_PAINTWM_TIMER,具有比张贴继续消息较低的优先级.因此,如果您await Task.Yield()在紧密循环中执行操作,您仍可能会阻止UI.

这就是setTimer你在问题中提到的JavaScript的类比.一个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++;
    }
}
Run Code Online (Sandbox Code Playgroud)

对于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;
}
Run Code Online (Sandbox Code Playgroud)

对于在UI线程上运行的此类后台操作的每次迭代,建议您不要超过50毫秒.

对于没有同步上下文的非UI线程,await Task.Yield()只需将continuation切换到随机池线程.不能保证它将与当前线程不同,它只能保证是异步延续.如果ThreadPool正在挨饿,它可以将延续计划到同一个线程上.

在ASP.NET中,await Task.Yield()除了@ StephenCleary的答案中提到的解决方法之外,完成任务都没有意义.否则,它只会损害使用冗余线程切换的Web应用程序性能.

那么,await Task.Yield()有用吗?国际海事组织,并不多.它可以用作运行continuation via的快捷方式,SynchronizationContext.Post或者ThreadPool.QueueUserWorkItem,如果你真的需要在方法的一部分上强加异步.

关于你引用的书籍,我认为这些使用方法Task.Yield是错误的.我在上面解释了为什么他们错误的UI线程.对于非UI池线程,除了运行像Stephen Toub这样的自定义任务泵之外,根本没有"要执行的线程中的其他任务".AsyncPump

更新以回答评论:

...怎样才能进行异步操作并保持在同一个线程中?

举个简单的例子:WinForms app:

async void Form_Load(object s, object e) 
{ 
    await Task.Yield(); 
    MessageBox.Show("Async message!");
}
Run Code Online (Sandbox Code Playgroud)

Form_Load将返回调用者(已触发Load事件的WinFroms框架代码),然后在消息循环的某个未来迭代运行时异步显示消息框Application.Run().延续回调排队WinFormsSynchronizationContext.Post,在内部将私有Windows消息发布到UI线程的消息循环.当此消息被泵送时,仍将在同一线程上执行回调.

在控制台应用程序中,您可以运行与AsyncPump上面提到的类似的序列化循环.

  • @bN_,有:`protected virtual Task OverridableMethod(){return Task.Task.CompletedTask; }`.注意`async`不是虚方法签名的一部分,因此在派生类中重写此方法时仍可以使用它. (2认同)

Ste*_*ary 18

我只发现Task.Yield在两种情况下有用:

  1. 单元测试,以确保被测代码在异步存在的情况下正常工作.
  2. 解决一个模糊的ASP.NET问题,其中身份代码无法同步完成.


Guf*_*ffa 7

不,这与使用setTimeout将控制权返回到UI完全不同.在Javascript中,总是让UI更新,因为setTimeout总是有几毫秒的最小暂停,并且挂起的UI工作优先于定时器,但await Task.Yield();不会这样做.

有没有保证的收益率将让任何工作在主线程来完成,相反调用该收益率将超过经常工作UI优先的代码.

"大多数UI环境中UI线程上存在的同步上下文通常会优先考虑发布到上下文的工作,而不是输入和渲染工作.因此,不要依赖等待Task.Yield();来保持UI响应".

参考:MSDN:Task.Yield方法