我一直在阅读有关Task.Yield
的信息,作为一名Javascript开发人员,我可以说它的工作是 完全 与setTimeout(function (){...},0);
相同在让主要单线程处理其他东西方面也是如此:
“不要全力以赴,从时间上释放 - 所以其他人会这样做 还有一些......“
在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我无法打开任务( 是的我知道我实际上可以使用Web worker )。但是使用c#我可以。
如果是这样 - 为什么在我可以释放它的时候甚至会不时发布?
添加参考:
来自here:
来自here(另一本电子书):
答案 0 :(得分:55)
当你看到:
await Task.Yield();
你可以这样思考:
await Task.Factory.StartNew(
() => {},
CancellationToken.None,
TaskCreationOptions.None,
SynchronizationContext.Current != null?
TaskScheduler.FromCurrentSynchronizationContext():
TaskScheduler.Current);
所有这一切都确保延续将在未来异步发生。通过异步我的意思是执行控件将返回到async
方法的调用者,并且延续回调将不发生在同一堆栈帧上。< / p>
什么时候完全取决于调用者线程的同步上下文。
对于UI线程,继续将在消息循环的某个未来迭代中发生,由Application.Run
(WinForms)或Dispatcher.Run
运行({ {3}})。在内部,它归结为Win32 PostMessage
API,它将自定义消息发布到UI线程的消息队列。当此消息被泵送和处理时,将调用await
延续回调。你究竟何时会发生这种情况,你完全失控。
此外,Windows有自己的优先级来提取消息:WPF。最相关的部分:
根据该计划,优先级可以被视为三级。所有 发布的消息优先于用户输入消息,因为 他们居住在不同的队列中。并且所有用户输入消息都是 优先级高于WM_PAINT和WM_TIMER消息。
因此,如果您使用await Task.Yield()
屈服于消息循环以试图保持UI响应,那么您实际上有可能阻碍UI线程的消息循环。某些待处理用户输入消息以及WM_PAINT
和WM_TIMER
的优先级低于发布的延续消息。因此,如果您在紧密循环中执行await Task.Yield()
,则仍可能会阻止UI。
这与您在问题中提到的JavaScript setTimer
类比有所不同。在浏览器的消息泵处理了所有用户输入消息后,{em}将调用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;
}
INFO: Window Message Priorities,对于在UI线程上运行的此类后台操作的每次迭代,您不超过50毫秒。
对于没有同步上下文的非UI线程,await Task.Yield()
只是将连续切换为随机池线程。无法保证它将成为当前线程的不同的线程,它只能保证是异步延续。如果ThreadPool
正在挨饿,它可能会将延续计划到同一个线程上。
在ASP.NET 中,除It is recommended中提到的解决方法外,执行await Task.Yield()
根本没有意义。否则,它只会损害使用冗余线程切换的Web应用程序性能。
那么,await Task.Yield()
有用吗? IMO,并不多。它可以用作通过SynchronizationContext.Post
或ThreadPool.QueueUserWorkItem
运行延续的快捷方式,如果您确实需要在方法的一部分上强加异步。
关于您引用的图书,在我看来,使用Task.Yield
的方法是错误的。我解释了为什么他们在上面的UI线程中出错了。对于非UI池线程,除了运行像@StephenCleary's answer这样的自定义任务泵之外,线程中没有&#34;其他任务执行&#34; 。
更新以回复评论:
...如何进行异步操作并保持在同一个线程中 ?..
举个简单的例子:WinForms app:
async void Form_Load(object s, object e)
{
await Task.Yield();
MessageBox.Show("Async message!");
}
Form_Load
将返回调用者(WinFroms框架代码已触发Load
事件),然后消息框将异步显示,在将来迭代的消息循环运行{ {1}}。延续回调与Application.Run()
排队,后者在内部将私有Windows消息发布到UI线程的消息循环。当此消息被泵送时,将在同一线程上执行回调。
在控制台应用中,您可以使用上面提到的WinFormsSynchronizationContext.Post
运行类似的序列化循环。
答案 1 :(得分:17)
我发现Task.Yield
在两种情况下都很有用:
答案 2 :(得分:6)
不,这与使用setTimeout
将控制权返回给UI完全不同。在总是让UI更新的Javascript中,setTimeout
始终具有几毫秒的最小暂停,并且挂起的UI工作优先于计时器,但await Task.Yield();
不会这样做。
无法保证yield会让任何工作在主线程中完成,相反,调用yield的代码通常会优先于UI工作。
“大多数UI中UI线程上存在的同步上下文 环境通常会优先考虑发布到上下文的工作 比输入和渲染工作。因此,不要依赖等待 Task.Yield();保持用户界面的响应。“
答案 3 :(得分:2)
首先让我澄清一下:Yield
与setTimeout(function (){...},0);
不完全相同。 JS在单线程环境中执行,因此这是让其他活动发生的唯一方法。有点cooperative multitasking。 .net在具有显式多线程的抢占式多任务环境中执行。
现在回到Thread.Yield
。正如我所说.net生活在先发制人的世界,但它比那复杂一点。 C#await/async
创建了由状态机统治的多任务模式的有趣混合。因此,如果您从代码中省略Yield
,那么它只会阻止该线程。如果你把它作为一个常规任务而只是调用start(或一个线程)那么它就会并行执行它的东西,然后在调用task.Result时阻塞调用线程。执行await Task.Yield();
时会发生什么变得更复杂。从逻辑上讲,它取消阻塞调用代码(类似于JS)并继续执行。它实际上做了什么 - 它选择另一个线程并在抢先环境中继续执行它与调用线程。所以它是在调用线程直到第一个Task.Yield
然后它在它自己的上面。对Task.Yield
的后续调用显然没有做任何事情。
简单演示:
class MainClass
{
//Just to reduce amont of log itmes
static HashSet<Tuple<string, int>> cache = new HashSet<Tuple<string, int>>();
public static void LogThread(string msg, bool clear=false) {
if (clear)
cache.Clear ();
var val = Tuple.Create(msg, Thread.CurrentThread.ManagedThreadId);
if (cache.Add (val))
Console.WriteLine ("{0}\t:{1}", val.Item1, val.Item2);
}
public static async Task<int> FindSeriesSum(int i1)
{
LogThread ("Task enter");
int sum = 0;
for (int i = 0; i < i1; i++)
{
sum += i;
if (i % 1000 == 0) {
LogThread ("Before yield");
await Task.Yield ();
LogThread ("After yield");
}
}
LogThread ("Task done");
return sum;
}
public static void Main (string[] args)
{
LogThread ("Before task");
var task = FindSeriesSum(1000000);
LogThread ("While task", true);
Console.WriteLine ("Sum = {0}", task.Result);
LogThread ("After task");
}
}
以下是结果:
Before task :1
Task enter :1
Before yield :1
After yield :5
Before yield :5
While task :1
Before yield :5
After yield :5
Task done :5
Sum = 1783293664
After task :1
如果您在方法之上移动Task.Yield
,它将从头开始异步,并且不会阻止调用线程。
结论:Task.Yield
可以混合同步和异步代码。一些或多或少的现实场景:你有一些繁重的计算操作和本地缓存和任务CalcThing
。在此方法中,检查项是否在缓存中,如果是 - 返回项,如果它不在Yield
并继续计算它。实际上,你书中的样本毫无意义,因为那里没有任何有用的东西。他们关于GUI交互性的说法很糟糕且不正确(UI线程将被锁定,直到第一次调用Yield
,你永远不应该这样做,MSDN明确(并且正确):&#34;不依赖于等待Task.Yield();以保持UI响应&#34;。
答案 4 :(得分:0)
您假设长时间运行的功能是可以在后台线程上运行的功能。如果不是,例如因为它具有UI交互,则无法阻止UI在运行时阻塞,因此运行的时间应保持足够短,不会给用户带来问题。
另一种可能性是你有比后台线程更长时间运行的功能。在这种情况下,可能更好(或者可能无关紧要,取决于),以防止其中一些功能占用你的所有线程。
答案 5 :(得分:0)
我认为在使用Task.Yield时没有人提供真正的答案。 如果任务使用永无止境的循环(或冗长的同步作业),并且可能潜在地仅持有线程池线程(不允许其他任务使用此线程),则最需要使用该线程。如果代码在循环内部同步运行,则会发生这种情况。 Task.Yield重新安排任务到线程池队列,其他等待线程的任务也可以执行。
示例:
CancellationTokenSource cts;
void Start()
{
cts = new CancellationTokenSource();
// run async operation
var task = Task.Run(() => SomeWork(cts.Token), cts.Token);
// wait for completion
// after the completion handle the result/ cancellation/ errors
}
async Task<int> SomeWork(CancellationToken cancellationToken)
{
int result = 0;
bool loopAgain = true;
while (loopAgain)
{
// do something ... means a substantial work or a micro batch here - not processing a single byte
loopAgain = /* check for loop end && */ cancellationToken.IsCancellationRequested;
if (loopAgain) {
// reschedule the task to the threadpool and free this thread for other waiting tasks
await Task.Yield();
}
}
cancellationToken.ThrowIfCancellationRequested();
return result;
}
void Cancel()
{
// request cancelation
cts.Cancel();
}