gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-11-30 16:59:57 26 4
gpt4 key购买 nike

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

我目前正在使用一个自定义等待程序,它只是将协程对象上的延续排队。当协程想要让步时,它会等待这个自定义等待者。当协程恢复时,它只是调用延续。

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

一个例子是这样的:

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 ,并找到了我认为是执行延续的地方。虽然这个函数的路径看起来很复杂,但我无法确定到底是什么调用导致调用这个函数(但我假设它是某个编译器生成的调用)。

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

  • 当前线程正在中止

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

  • IsValidLocationForInlining 为 false

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

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

此外,我知道任务库从 .NET 4 到 .NET 4.5 发生了很大变化。据我所知,引用源是针对 .NET 4.5 的,所以我想知道是否有人知道这种行为是否发生了变化。我将主要通过 Microsoft.Bcl.Async 在 .NET 4.0 上使用协程库,它在这里似乎也能正常工作。

最佳答案

I'm trying to implement coroutines using async/await, and for that I want to ensure my coroutines are only executing on one thread (the thread that resumes them).

我认为您可以放心地依赖这种行为。只要您不使用以下任何功能,这应该是正确的:

  • 自定义 TPL 任务调度器;
  • 自定义同步上下文;
  • ConfiguredTaskAwaitableTask.ConfigureAwait();
  • YieldAwaitableTask.Yield();
  • Task.ContinueWith();
  • 任何可能导致线程切换的东西,例如异步 I/O API、Task.Delay()Task.Run()Task .Factory.StartNew(), ThreadPool.QueueUserWorkItem()

您在这里唯一使用的是 TaskAwaiter,下面将详细介绍它。

首先,您不必担心任务内部的线程切换,您可以在其中执行 await Coroutine.Yield()。代码将在您显式调用继续回调的同一线程上恢复,您可以完全控制它。

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

因此,您唯一需要担心的是线程切换,它可能会在您等待 Test() 返回的任务结果时发生。这就是 TaskAwaiter 发挥作用的地方。

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

如果您不想依赖它,您可以创建另一个自定义等待程序来替换协程任务的 TaskAwaiter。您可以使用 Task.ContinueWith(TaskContinuationOptions.ExecuteSynchronously) 实现它,Stephen Toub 在他的 "When "“ExecuteSynchronously” doesn't execute synchronously" 中非正式地记录了该行为博客文章。综上所述,ExecuteSynchronously continuation 在以下严重情况下不会被内联:

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

关于c# - 在任务上排队的延续是否总是在完成任务的线程上执行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22975072/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com