Task.Yield - 实际用途? [英] Task.Yield - real usages?

查看:34
本文介绍了Task.Yield - 实际用途?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

<块引用>

不要拿走所有的权力,从时间中释放出来——所以其他人会也来点吧……"

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

但我看到了这个例子 (另一本电子书):

解决方案

当你看到:

await Task.Yield();

你可以这样想:

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

所有这一切都是为了确保延续将在未来异步发生.异步我的意思是执行控制将返回给async方法的调用者,而continuation回调不会发生在同一个堆栈上框架.

它何时发生以及在哪个线程上发生完全取决于调用者线程的同步上下文.

对于 UI 线程,将在消息循环的某个未来迭代中发生延续,由 Application.Run (WinForms) 或 Dispatcher.Run (WPF).在内部,它归结为 Win32 PostMessage API,它将自定义消息发布到 UI 线程的消息队列.await 继续回调将在此消息被抽取和处理时被调用.你完全无法控制这到底什么时候会发生.

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

<块引用>

在此方案下,优先级可被视为三级.全部发布的消息比用户输入的消息具有更高的优先级,因为它们驻留在不同的队列中.并且所有用户输入消息都是比 WM_PAINT 和 WM_TIMER 消息的优先级更高.

因此,如果您使用 await Task.Yield() 来让步于消息循环以尝试保持 UI 响应,则实际上您有阻碍 UI 线程的消息循环的风险.一些待处理的用户输入消息,以及 WM_PAINTWM_TIMER 的优先级低于发布的继续消息.因此,如果您在紧密循环中执行 await Task.Yield(),您仍然可能会阻塞 UI.

这就是它与您在问题中提到的 JavaScript 的 setTimer 类比的不同之处.setTimer 回调将在 浏览器的消息泵处理完所有用户输入消息后调用.

所以,await Task.Yield() 不适合在 UI 线程上做后台工作.事实上,您很少需要在 UI 线程上运行后台进程,但有时您会这样做,例如编辑器语法高亮、拼写检查等.在这种情况下,请使用框架的空闲基础设施.

例如,使用 WPF 你可以做 await Dispatcher.Yield(DispatcherPriority.ApplicationIdle):

async Task DoUIThreadWorkAsync(CancellationToken token){变量 i = 0;而(真){token.ThrowIfCancellationRequested();等待 Dispatcher.Yield(DispatcherPriority.ApplicationIdle);//做 UI 相关的工作项this.TextBlock.Text = "迭代";+ 我++;}}

对于 WinForms,您可以使用 Application.Idle 事件:

//等待 IdleYield();公共静态任务 IdleYield(){var idleTcs = new TaskCompletionSource();//订阅 Application.IdleEventHandler 处理程序 = null;处理程序 = (s, e) =>{Application.Idle -= 处理程序;idleTcs.SetResult(true);};Application.Idle += 处理程序;返回 idleTcs.Task;}

建议在UI线程上运行的此类后台操作的每次迭代不要超过50ms.

对于没有同步上下文的非 UI 线程await Task.Yield() 只是将延续切换到随机池线程.不能保证它将成为与当前线程不同的线程,它只能保证是一个异步延续.如果 ThreadPool 处于饥饿状态,它可能会将延续安排到同一线程上.

在 ASP.NET 中,除了 @StephenCleary 的回答.否则,冗余线程切换只会损害 Web 应用程序的性能.

那么,await Task.Yield() 有用吗? IMO,不多.如果您确实需要对方法的一部分施加异步,它可以用作通过 SynchronizationContext.PostThreadPool.QueueUserWorkItem 运行延续的快捷方式.>

关于您引用的书籍,在我看来,那些使用 Task.Yield 的方法是错误的.我在上面解释了为什么它们对于 UI 线程是错误的.对于非 UI 池线程,根本没有线程中要执行的其他任务",除非您运行像 Stephen Toub 的 AsyncPump.

更新以回答评论:

<块引用>

... 怎么可能是异步操作,并保持在同一个线程中?..

举一个简单的例子:WinForms 应用程序:

async void Form_Load(object s, object e){等待 Task.Yield();MessageBox.Show("异步消息!");}

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

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

I've been reading about Task.Yield , And as a Javascript developer I can tell that's it's job is exactly the same as setTimeout(function (){...},0); in terms of letting the main single thread deal with other stuff aka :

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

In js it's working particular in long loops. ( don't make the browser freeze...)

But I saw this example 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;
}

As a JS programmer I can understand what they did here.

BUT as a C# programmer I ask myself : why not open a task for it ?

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

Question

With Js I can't open a Task (yes i know i can actually with web workers) . But with c# I can.

If So -- why even bother with releasing from time to time while I can release it at all ?

Edit

Adding references :

From here :

From here (another ebook):

解决方案

When you see:

await Task.Yield();

you can think about it this way:

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

All this does is makes sure the continuation will happen asynchronously in the future. By asynchronously I mean that the execution control will return to the caller of the async method, and the continuation callback will not happen on the same stack frame.

When exactly and on what thread it will happen completely depends on the caller thread's synchronization context.

For a UI thread, the continuation will happen upon some future iteration of the message loop, run by Application.Run (WinForms) or Dispatcher.Run (WPF). Internally, it comes down to the Win32 PostMessage API, which post a custom message to the UI thread's message queue. The await continuation callback will be called when this message gets pumped and processed. You're completely out of control about when exactly this is going to happen.

Besides, Windows has its own priorities for pumping messages: INFO: Window Message Priorities. The most relevant part:

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.

So, if you use await Task.Yield() to yield to the message loop in attempt to keep the UI responsive, you are actually at risk of obstructing the UI thread's message loop. Some pending user input messages, as well as WM_PAINT and WM_TIMER, have a lower priority than the posted continuation message. Thus, if you do await Task.Yield() on a tight loop, you still may block the UI.

This is how it is different from the JavaScript's setTimer analogy you mentioned in the question. A setTimer callback will be called after all user input message have been processed by the browser's message pump.

So, await Task.Yield() is not good for doing background work on the UI thread. In fact, you very rarely need to run a background process on the UI thread, but sometimes you do, e.g. editor syntax highlighting, spell checking etc. In this case, use the framework's idle infrastructure.

E.g., with WPF you could do 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++;
    }
}

For WinForms, you could use Application.Idle event:

// 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 that you do not exceed 50ms for each iteration of such background operation running on the UI thread.

For a non-UI thread with no synchronization context, await Task.Yield() just switches the continuation to a random pool thread. There is no guarantee it is going to be a different thread from the current thread, it's only guaranteed to be an asynchronous continuation. If ThreadPool is starving, it may schedule the continuation onto the same thread.

In ASP.NET, doing await Task.Yield() doesn't make sense at all, except for the workaround mentioned in @StephenCleary's answer. Otherwise, it will only hurt the web app performance with a redundant thread switch.

So, is await Task.Yield() useful? IMO, not much. It can be used as a shortcut to run the continuation via SynchronizationContext.Post or ThreadPool.QueueUserWorkItem, if you really need to impose asynchrony upon a part of your method.

Regarding the books you quoted, in my opinion those approaches to using Task.Yield are wrong. I explained why they're wrong for a UI thread, above. For a non-UI pool thread, there's simply no "other tasks in the thread to execute", unless you running a custom task pump like Stephen Toub's AsyncPump.

Updated to answer the comment:

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

As a simple example: WinForms app:

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

Form_Load will return to the caller (the WinFroms framework code which has fired Load event), and then the message box will be shown asynchronously, upon some future iteration of the message loop run by Application.Run(). The continuation callback is queued with WinFormsSynchronizationContext.Post, which internally posts a private Windows message to the UI thread's message loop. The callback will be executed when this message gets pumped, still on the same thread.

In a console app, you can run a similar serializing loop with AsyncPump mentioned above.

这篇关于Task.Yield - 实际用途?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆