【问题标题】:Scheduling Task vs. Task Creation调度任务与任务创建
【发布时间】:2014-07-05 20:08:52
【问题描述】:

我对这个主题很困惑。我的假设是任务创建和它的调度应该严格分开,在 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 接缝(至少在代码中)仅是任务创建 - 系统没有要求调度,那么为什么以及谁安排继续运行?显然这里的创建/运行原则被打破了。

await 也有类似的情况。考虑一下这个众所周知的article 中的伪代码。根据伪代码 await 被翻译成延续任务并返回给调用者,我希望必须安排任务才能完成它。也不是这种情况,请考虑以下代码:-

static async Task foo()
        {
            await bar();
        }

        static async Task bar()
        {
            print("bar");
        }

        static void Main(string[] args)
        {
            foo();

            Thread.Sleep(1000);
            return;
        }

bar 将在没有专门调度 foo 创建的 Task 对象的情况下执行。

所以问题是:

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

  2. await 不仅会创建文章中显示的继续任务,而且如果可能的话还可以调度(Call Post on SynchronizationContext 或 TaskScheduler)是否正确。

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

【问题讨论】:

  • 您的样本是完全同步的 - 所以所有部分都是同步执行的。使用async 关键字不会神奇地将同步代码变为异步代码。使用Task.Delay(1000) 或类似代码来实际拥有异步代码。
  • @Alexei 这是否同步无关紧要。问题是为什么它们(cont & bar)会被执行。您可以轻松地将其转换为异步案例,但问题将保持不变。谁安排功能。
  • “时间表”?您调用函数 (foo()) 并执行它 - 有什么令人惊讶的?也许你想了解async 是如何实现的——试试这个搜索bing.com/search?q=c%23+async+internals
  • @Boris,您仍然可以安排由async 方法创建的任务,例如this,虽然很少需要。

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


【解决方案1】:

您可以指定TaskScheduler 以在ContinueWith 的某些重载中使用。 决定在哪里运行该代码。此处不能指定调度器是不正确的。

在第一个等待点之后,在捕获的SynchronizationContext 或当前TaskScheduler 上运行异步方法。所以确实异步方法确实安排了延续。 (我忽略了您可以拥有自定义等待者这一事实。)

您的异步示例在主线程上同步运行以完成。

ContinueWith 不仅创建任务而且调度它是否正确。

是的,在您指定的调度程序上。

await 不仅会创建文章中出现的继续任务,而且如果可能的话还可以调度(Call Post on SynchronizationContext 或 TaskScheduler)是否正确。

是的,在第一次等待(不会立即完成)之后发生调度操作。

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

async/await 的内部非常复杂。引擎盖下有很多机械和非平凡的行为。异步方法的代码实际运行的线程尤其令人惊讶且不一致。此功能的设计者显然很难在所有重要场景中开箱即用。这导致 Stack Overflow 上每天都有很多关于边缘情况和非常令人惊讶的行为的问题。

可以使用很少使用的Task.Start (TaskScheduler) 方法来解开创建和调度。

对于延续,此模型行不通。当先行词完成时,必须激活延续。如果没有 TaskScheduler 来执行该操作,则无法继续运行。这就是为什么必须在设置延续时指定调度程序的原因。

对于异步方法,您也可以使用自定义等待程序来解开创建和调度。或者使用更简单的模型,例如await Task.Factory.StartNew(..., myScheduler)

bar 将在不专门调度 foo 创建的 Task 对象的情况下执行。

此任务不是基于 CPU 的任务。它是从不安排的。这是由TaskCompletionSource 支持的任务。在这里指定调度程序没有意义。

【讨论】:

  • 谢谢,我几乎理解了你所有的回答:)。但是,您能否详细说明(或提供链接)解释最后一个“......此任务不是基于 CPU 的任务。它永远不会被调度......”这句话。
  • 任务不必与可执行代码相关联。它可以表示任意事件。查看 TaskCompletionSource 的文档。每个异步方法都返回一个 TaskCompletionSource.Task 并在完成后在内部调用 TaskCompletionSource.SetXxx。 (当然,调用者无法检测到这一事实。实现细节。)希望有所帮助。如果没有,请询​​问。
  • 好的,谢谢让我学习这个主题,所以我会提出聪明的问题,同时将其标记为答案。
  • @usr,挑剔+1:没有Task.Run(..., myScheduler) 覆盖,你的意思是Task.Factory.StartNew(..., myScheduler)
  • @Noseratio 谢谢,已修复。这显示了我使用自定义 TaskScheduler 的频率。
【解决方案2】:

ContinueWith 不仅创建任务而且调度它是正确的吗?

ContinueWith 将创建Task 并使用TaskScheduler 来执行提供给它的委托。如果没有显式传递TaskScheduler,它将使用TaskScheduler.Current 来执行延续。

await 不仅会创建文章中出现的继续任务,而且如果可能的话还可以调度(Call Post on SynchronizationContext 或 TaskScheduler)是否正确。

await 不会创建任何Task。 await 关键字用于可等待类型,例如Task。当编译器点击await 关键字时,它会将方法提升到状态机中,该状态机还负责捕获当前正在使用的SynchronizationContext。除非被告知不要(使用ConfigureAwait(false)),否则它会将延续编组回到相同的上下文中。

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

await 的情况下,创建不是由关键字完成的,而是由调用的可等待方法完成的。 在Task.RunTask.Factory.Startnew的情况下,Task的创建是由该方法完成的,用户必须显式调用ContinueWith才能设置所述继续的调度。

【讨论】:

  • 当我说“await 不仅创建任务用于继续”时,我与这篇文章相关的伪代码 - blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259049.aspx 可以看到,await 被翻译成 ContiueWith 调用
  • await 将导致您的方法的其余部分(因此您的继续)被提升到该状态机并执行完成。简单来说,它会为您执行ContinueWith,而无需显式调用它。但实际上,幕后的事情要复杂得多
【解决方案3】:

问题源于对 async/await 模式的误解。

aync 不调度异步执行的方法。它只是标记了模式的使用,影响了调用者和被调用者处理返回值的方式。

创建 async/await 模式是为了替换围绕完成回调构建的旧模式,从而降低相关代码的复杂性。

参考: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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-28
    • 1970-01-01
    • 2012-08-30
    • 1970-01-01
    相关资源
    最近更新 更多