繁体   English   中英

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

[英]Are continuations queued on a Task always executed on the thread that finishes the task?

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

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

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

一个例子是这样的:

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();
}

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

现在,从这个函数来看,似乎延迟没有内联如果:

  • 当前线程正在中止

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

  • IsValidLocationForInlining为false

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

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

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

我正在尝试使用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()返回的任务。 正如上面提到的,这里面的任务线程切换可能不会发生,除非您通过自定义awaiter调用延续回调之前做明确。

所以,你唯一关心的是一个线程切换,它可能发生在你正在等待Test()返回的任务结果的时候。 这就是TaskAwaiter发挥作用的地方。

TaskAwaiter的行为没有记录。 据我可以从参考消息来源告诉, IsValidLocationForInlining未对观察TaskContinuation一种延续(通过创建TaskAwaiter )。 当前行为如下:如果当前线程已中止或当前线程的同步上下文与TaskAwaiter捕获的同步上下文不同,则不会内联。

如果您不想依赖于此,则可以创建另一个自定义awaiter来替换TaskAwaiter以执行您的协同任务。 你可以使用Task.ContinueWith( TaskContinuationOptions.ExecuteSynchronously)实现它,这个行为是由Stephen Toub在他的“When”“ExecuteSynchronously”不同步执行的“博客文章”中非正式记录的。总之, ExecuteSynchronously继续不会是在以下严苛条件下内联:

  • 当前线程被中止;
  • 当前线程有堆栈溢出;
  • 目标任务调度程序拒绝内联(但您没有使用自定义任务调度程序;默认情况下始终会在可能的情况下提升内联)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM