我一直在阅读 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 :
来自 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_PAINT
和 WM_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/