我对这个问题很困惑。我假设任务创建及其调度应该严格分开,哪些接缝不是C#中的情况。
考虑以下简单代码。
static async Task simpleton()
{
print("simpleton");
}
static async Task cont()
{
print("cont");
}
static void Main(string[] args)
{
Task t1 = simpleton();
Task t2 = t1.ContinueWith((a) => { cont(); });
Thread.Sleep(1000);
return;
}
输出
simpleton
cont
simpleton函数运行并创建任务t1(已经完成) - 这没关系。然而,t2接缝(至少在代码中)只创建任务 - 系统没有要求调度,那么为什么以及谁来安排延续运行?显然,这里打破了创建/运行原则。
类似的情况正等待着。考虑这个众所周知的article中的伪代码。根据伪代码,await被转换为continuation任务并返回给调用者,我希望它必须安排任务才能完成它。此情况并非如此,请考虑以下代码: -
static async Task foo()
{
await bar();
}
static async Task bar()
{
print("bar");
}
static void Main(string[] args)
{
foo();
Thread.Sleep(1000);
return;
}
栏将在没有专门安排由foo创建的Task对象的情况下执行。
所以问题是:
ContinueWith不仅创建任务而且还安排任务,这是否正确。
是否正确等待不仅创建了文章中显示的继续任务,而且如果可能还安排调度(在SynchronizationContext或TaskScheduler上调用Post)。
为什么async / await语言设计师会采用这种设计(调度和创建混合)?
答案 0 :(得分:4)
您可以指定在TaskScheduler
的某些重载中使用的ContinueWith
。 您决定运行该代码的位置。这里不能指定调度程序。
在第一个等待点之后,异步方法在捕获的SynchronizationContext
或当前TaskScheduler
上运行。所以异步方法确实会安排延续。 (我遗漏了你可以拥有自定义等待者的事实。)
您的异步示例在主线程上同步运行以完成。
ContinueWith不仅创建任务而且还安排任务,这是否正确。
是,在您指定的计划程序上。
是否正确等待不仅创建了文章中显示的继续任务,而且如果可能还安排调度(在SynchronizationContext或TaskScheduler上调用Post)。
是的,在第一次等待(没有立即完成)之后,就会发生调度操作。
为什么async / await语言设计师会采用这种设计(调度和创建混合)?
异步/等待的内部非常复杂。引擎盖下有很多机械和非平凡的行为。特别令人惊讶的是,异步方法的代码将在什么线程中实际运行。在所有重要场景中,这个功能的设计者显然很难将这项工作开箱即用。这导致每天都有关于边缘情况和非常令人惊讶的行为的Stack Overflow问题。
你可以使用很少使用的Task.Start (TaskScheduler)
方法来解开创建和日程安排。
对于延续,此模型无效。当先行者完成后,必须激活继续。如果没有TaskScheduler
,则无法继续运行。这就是为什么必须在设置延续时指定调度程序。
对于异步方法,您可以使用自定义awaiter解开创建和调度。或者使用更简单的模型,例如await Task.Factory.StartNew(..., myScheduler)
。
栏将在没有专门安排由foo创建的Task对象的情况下执行。
此任务不是基于CPU的任务。 永远不会安排。这是由TaskCompletionSource
支持的任务。指定调度程序在这里没有意义。
答案 1 :(得分:1)
ContinueWith不仅创建任务而且还安排任务,这是否正确。
ContinueWith
将创建Task
并使用TaskScheduler
来执行提供给它的委托。如果没有显式传递TaskScheduler
,它将使用TaskScheduler.Current
来执行延续。
是否正确等待不仅创建了文章中显示的继续任务,而且如果可能还安排调度(在SynchronizationContext或TaskScheduler上调用Post)。
await
不会创建任何Task
。 await关键字用于等待类型,例如Task
。当编译器命中await
关键字时,它会将方法提升到状态机,该状态机还负责捕获正在使用的当前SynchronizationContext
。除非被告知(使用ConfigureAwait(false)
),否则它会将延续编组回到相同的上下文中。
为什么async / await语言设计师会采用这种设计(调度和创建混合)?
在await
的情况下,创建不是由关键字完成的,创建是通过调用的等待方法完成的。
在Task.Run
或Task.Factory.Startnew
的情况下,方法正在创建Task
,并且用户必须显式调用ContinueWith
才能设置调度上述延续。
答案 2 :(得分:0)
问题源于对异步/等待模式的误解。
aync不会为异步执行安排方法。它只是标记模式的使用,影响调用者和被调用者处理返回值的方式。
创建了异步/等待模式以替换围绕完成回调构建的旧模式,从而降低了相关代码的复杂性。
请参阅:http://msdn.microsoft.com/en-us/library/hh191443.aspx
在该示例中,您看到的所有代码都不是作为异步任务执行的。相反,async和await会导致代码的某些位被重组为完成处理程序(对您来说是不可见的),但实际上就是它的执行方式。
如果要启动异步操作(启动和离开),您仍然需要使用类似System.Threading的内容:http://msdn.microsoft.com/en-us/library/a9fyxz7d(v=vs.110).aspx
或Task.Run:http://msdn.microsoft.com/en-us/library/system.threading.tasks.task_methods(v=vs.110).aspx