是否总是在完成任务的线程上执行在任务上排队的延续?

时间:2014-04-09 22:20:59

标签: c# .net multithreading asynchronous task-parallel-library

我正在尝试使用async / await实现协程,为此我想确保我的协程只在一个线程(恢复它们的线程)上执行。

我目前正在使用自定义awaiter,它只是对协程对象进行排队。当一个协程想要屈服时,它等待着这个定制的等待者。当一个协程恢复时,它只是调用继续。

我可以保证每个简历只排队一次,即。我们不会在没有等待的情况下创建多个任务。我还可以保证我们只会等待最终等待定制等待者或等待定制等待者的其他任务的任务。也就是说,我们不会欺骗任何“外部”任务。

一个例子是这样的:

private static async Task Sleep(int ms)
{
  Stopwatch timer = Stopwatch.StartNew();
  do
  {
    await Coroutine.Yield();
  }
  while (timer.ElapsedMilliseconds < ms);
}

private static async Task Test()
{
    // Second resume
    await Sleep(1000);
    // Unknown how many resumes
}

private static async Task Main()
{
    // First resume
    await Coroutine.Yield();
    // Second resume
    await Test();
}

这一切似乎都有效,似乎任务的延续确实是在同一个线程上内联执行的。我只是想确保这种行为是一致的,我可以依赖它。 I was able to check the reference source,并且已经找到了我认为继续执行的地方。这个函数的路径看起来相当复杂,我无法确定究竟是什么调用导致调用此函数(但我认为它是一些编译器生成的调用)。

现在,从这个函数来看,如果出现以下情况,似乎没有内联:

  • 当前主题正在中止

这应该不是问题,因为当前线程正在自愿执行协程,如果我们正在中止,我们不应该执行协程。

  • IsValidLocationForInlining是假的

如果当前同步上下文不是默认值,或者当前任务调度程序不是默认值,则此属性为false。作为预防措施,我在继续执行协同程序期间执行SynchronizationContext.SetSynchronizationContext(null)。我还将确保任务调度程序是默认值。

现在,我的实际问题是我是否可以依赖这种行为。这个版本可能会在.NET版本中发生变化吗?实现自定义同步上下文是否更好,确保所有连续都由协程运行?

此外,我知道任务库已经从.NET 4改为.NET 4.5。据我所知,参考源是针对.NET 4.5的,所以我想知道是否有人知道这种行为是否已经改变。我将主要使用带有Microsoft.Bcl.Async的.NET 4.0上的协程库,它似乎在这里工作正常。

1 个答案:

答案 0 :(得分:2)

  

我试图使用async / await实现协程,为此我   想确保我的协程只在一个线程上执行(   恢复它们的线程。)

我认为你可以安全地依赖这种行为。只要您不使用以下任何功能,就应该这样:

  • 自定义TPL任务计划程序;
  • 自定义同步上下文;
  • ConfiguredTaskAwaitableTask.ConfigureAwait();
  • YieldAwaitableTask.Yield();
  • Task.ContinueWith();
  • 可能导致线程切换的任何内容,例如异步I / O API,Task.Delay()Task.Run()Task.Factory.StartNew()ThreadPool.QueueUserWorkItem()等。

您在这里使用的唯一内容是TaskAwaiter,下面会详细介绍。

首先,你不应该担心任务中的线程切换,你在await Coroutine.Yield()。代码将在您明确调用continuation回调的同一个线程上完全恢复,您可以完全控制它。

其次,这里唯一的Task对象是由状态机逻辑生成的(具体地,由AsyncTaskMethodBuilder生成)。这是Test()返回的任务。如上所述,此任务中的线程切换 可能不会发生,除非您在通过自定义等待者调用延续回调之前明确地执行此操作。

因此,您唯一关心的是线程切换,可能发生在您等待Test()返回的任务结果的位置。 TaskAwaiter发挥作用的地方。

TaskAwaiter的行为没有记录。据参考资料我所知,IsValidLocationForInlining类型的延续(由TaskContinuation创建)没有观察到TaskAwaiter。目前的行为如下:如果当前线程被中止或者当前线程的同步上下文与TaskAwaiter捕获的同步上下文不同,则不会内联延续。

如果您不想依赖此功能,可以创建另一个自定义等待工具来替换TaskAwaiter以执行您的协同任务。您可以使用Task.ContinueWith( TaskContinuationOptions.ExecuteSynchronously)来实现它,这个行为由Stephen Toub在他的"When "“ExecuteSynchronously” doesn't execute synchronously"博客文章中非正式记录。总而言之,在以下严苛条件下,ExecuteSynchronously延续不会被内联:

  • 当前主题已中止;
  • 当前线程有堆栈溢出;
  • 目标任务调度程序拒绝内联(但您不使用自定义任务计划程序;默认情况下,它总是在可能的情况下促进内联)。