调度任务与任务创建

时间:2014-07-05 20:08:53

标签: c# .net multithreading asynchronous async-await

我对这个问题很困惑。我假设任务创建及其调度应该严格分开,哪些接缝不是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对象的情况下执行。

所以问题是:

  1. ContinueWith不仅创建任务而且还安排任务,这是否正确。

  2. 是否正确等待不仅创建了文章中显示的继续任务,而且如果可能还安排调度(在SynchronizationContext或TaskScheduler上调用Post)。

  3. 为什么async / await语言设计师会采用这种设计(调度和创建混合)?

3 个答案:

答案 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.RunTask.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