非捕获Task.Yield的缺乏迫使我用Task.Run,为什么遵循? [英] The lack of non-capturing Task.Yield forces me to use Task.Run, why follow that?
问题描述
道歉提前,如果这个问题是见仁见智为主。的 Task.Yield 版本缺乏这将没有捕捉到执行上下文中已经讨论过这里。 显然,这个功能是present以某种形式在异步CTP的早期版本,但被删除,因为它很容易被误用。
Apologies in advance if this question is opinion-based. The lack of Task.Yield version which wouldn't capture the execution context was already discussed here. Apparently, this feature was present in some form in early versions of Async CTP but was removed because it could easily be misused.
IMO,这种特性可以作为很容易被滥用为 Task.Run
本身。下面是我的意思。想象一下,有一个awaitable SwitchContext.Yield
API,它的时间表延续上线程池,因此执行将一直持续在一个线程调用线程不同。在下面的code,它开始从UI线程一些CPU密集型的工作,我可以用它。我会考虑继续CPU密集型的工作,在一个线程池的简便方法:
IMO, such feature could be as easily misused as Task.Run
itself. Here's what I mean. Imagine there's an awaitable SwitchContext.Yield
API which schedules the continuation on ThreadPool, so the execution will always continues on a thread different from the calling thread. I could have used it in the following code, which starts some CPU-bound work from a UI thread. I would consider it a convenient way of continuing the CPU-bound work on a pool thread:
class Worker
{
static void Log(string format, params object[] args)
{
Debug.WriteLine("{0}: {1}", Thread.CurrentThread.ManagedThreadId, String.Format(format, args));
}
public async Task UIAction()
{
// UI Thread
Log("UIAction");
// start the CPU-bound work
var cts = new CancellationTokenSource(5000);
var workTask = DoWorkAsync(cts.Token);
// possibly await for some IO-bound work
await Task.Delay(1000);
Log("after Task.Delay");
// finally, get the result of the CPU-bound work
int c = await workTask;
Log("Result: {0}", c);
}
async Task<int> DoWorkAsync(CancellationToken ct)
{
// start on the UI thread
Log("DoWorkAsync");
// switch to a pool thread and yield back to the UI thread
await SwitchContext.Yield();
Log("after SwitchContext.Yield");
// continue on a pool thread
int c = 0;
while (!ct.IsCancellationRequested)
{
// do some CPU-bound work on a pool thread: counting cycles :)
c++;
// and use async/await too
await Task.Delay(50);
}
return c;
}
}
现在,没有 SwitchContext.Yield
, DoWorkAsync
看起来像下面。它增加了复杂性,一些额外水平的异步委托和任务的嵌套形式:
Now, without SwitchContext.Yield
, DoWorkAsync
would look like below. It adds some extra level of complexity in form of async delegate and task nesting:
async Task<int> DoWorkAsync(CancellationToken ct)
{
// start on the UI thread
Log("DoWorkAsync");
// Have to use async delegate
// Task.Run uwraps the inner Task<int> task
return await Task.Run(async () =>
{
// continue on a pool thread
Log("after Task.Yield");
int c = 0;
while (!ct.IsCancellationRequested)
{
// do some CPU-bound work on a pool thread: counting cycles :)
c++;
// and use async/await too
await Task.Delay(50);
}
return c;
});
}
这表示,实施 SwitchContext.Yield
实际上可能很简单,(我敢说)高效:
That said, implementing SwitchContext.Yield
may actually be quite simple and (I dare to say) efficient:
public static class SwitchContext
{
public static Awaiter Yield() { return new Awaiter(); }
public struct Awaiter : System.Runtime.CompilerServices.INotifyCompletion
{
public Awaiter GetAwaiter() { return this; }
public bool IsCompleted { get { return false; } }
public void OnCompleted(Action continuation)
{
ThreadPool.QueueUserWorkItem((state) => ((Action)state)(), continuation);
}
public void GetResult() { }
}
}
因此,我的问题是,,我为什么要preFER的第二个版本 DoWorkAsync
过的第一个,为什么会使用 SwitchContext.Yield
被认为是不好的做法?
So, my question is, why should I prefer the second version of DoWorkAsync
over the first one, and why would using SwitchContext.Yield
be considered a bad practice?
推荐答案
您不要的有无的投入在 Task.Run
DoWorkAsync
。考虑这个选项:
You don't have to put the Task.Run
in DoWorkAsync
. Consider this option:
public async Task UIAction()
{
// UI Thread
Log("UIAction");
// start the CPU-bound work
var cts = new CancellationTokenSource(5000);
var workTask = Task.Run(() => DoWorkAsync(cts.Token));
// possibly await for some IO-bound work
await Task.Delay(1000);
Log("after Task.Delay");
// finally, get the result of the CPU-bound work
int c = await workTask;
Log("Result: {0}", c);
}
这导致code有更清晰的意图。 DoWorkAsync
是一个自然同步方法,所以它有一个同步的签名。 DoWorkAsync
不知道也不关心用户界面。该 UIAction
,这确实对UI线程关怀,推动扫尾工作到使用后台线程 Task.Run
。
This results in code with much clearer intent. DoWorkAsync
is a naturally synchronous method, so it has a synchronous signature. DoWorkAsync
neither knows nor cares about the UI. The UIAction
, which does care about the UI thread, pushes off the work onto a background thread using Task.Run
.
作为一般规则,尝试推任何 Task.Run
调出你的库方法尽可能多地。
As a general rule, try to "push" any Task.Run
calls up out of your library methods as much as possible.
这篇关于非捕获Task.Yield的缺乏迫使我用Task.Run,为什么遵循?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!