【问题标题】:What happens when awaiting a call on a different thread等待不同线程上的调用时会发生什么
【发布时间】:2021-04-25 21:14:03
【问题描述】:

假设我像这样调用一个任务:

Task.Factory.StartNew(async () =>
                {
                    await SomeAction();
                }, TaskCreationOptions.LongRunning);

SomeAction 里面有这样一行:

await OtherAction();

使用常规await,代码会返回到调用上下文,但这里是在不同线程上运行的任务。那么那里的等待上下文会发生什么?在这种情况下真的像阻塞吗?

【问题讨论】:

    标签: c# async-await task-parallel-library


    【解决方案1】:

    使用常规等待,代码会返回到调用上下文,但这里是在不同线程上运行的任务。那么那里的等待上下文会发生什么?在这种情况下真的像阻塞吗?

    Here's how await worksawait 从不阻塞。

    await 将首先检查它的等待(通常是一个任务);如果 awaitable 已经完成,那么 await 将提取结果(或异常)并继续执行该方法(同步)。如果可等待对象已经完成,那么await 将异步运行。

    await 以异步方式运行时,它将(默认情况下)捕获当前“上下文”(SynchronizationContext.Current,或者,如果是null,则为TaskScheduler.Current),然后返回一个不完整的任务。由于示例代码使用Task.Factory.StartNew 而不指定TaskScheduler(IMO 始终是一个错误),因此它的委托在TaskScheduler.CurrentStartNew 被调用时执行。因此await 将捕获的上下文与TaskScheduler.Current 相同。

    如果 TaskScheduler.CurrentTaskScheduler.Default 相同(很可能但绝不保证),那么当async 方法返回时,线程返回到线程池,和since StartNew doesn't understand async lambdas,从StartNew返回的Task就完成了。这意味着那里的LongRunning 标志只会让事情变慢;这与优化相反。

    稍后,当await 完成时,它将继续在捕获的上下文(TaskScheduler.Current)上执行 lambda。 如果TaskScheduler.CurrentTaskScheduler.Default 相同,则 lambda 将在从线程池中新获取的线程上继续执行(可能是同一个线程,但也可能是不同的线程)。


    这听起来很复杂,但实际上这只是因为代码使用了StartNew。如果您使用Task.Run 查看代码,它会简单得多,因为上下文是always TaskScheduler.Default(线程池上下文):

    Task.Run(async () =>
    {
      await SomeAction();
    });
    

    现在你可以肯定地说,如果await异步动作,那么它会返回一个任务,将线程返回到线程池。当SomeAction 完成时,lambda 将继续在线程池线程上执行。此外,由于Task.Run 理解async lambda,所以从Task.Run 返回的任务在lambda 完成之前不会完成。

    以下是一些指导方针:

    【讨论】:

    • 能否详细说明 SynchronizationContext.Current、TaskScheduler.Current、TaskScheduler.Default 到底是什么以及它们之间的区别?
    • 另外,关于 Task.Run 的最后一部分,如果我理解正确并假设任务未完成,则任务在不同的线程上运行,而 Task.Run 的调用线程被调用从被返回到线程池?那么原始线程如何知道处理 lambda 的另一个线程已完成,并且他可以继续 Task.Run 之后的任何代码?
    • @YonatanNir: SynchronizationContext.CurrentTaskScheduler.Current 是表示当前 SyncCtx 和任务调度程序的属性。 TaskScheduler.Default 是线程池任务调度器。任务不在不同的线程上运行;真正的异步任务don't "run" on a thread at all。原线程调用Task.Run,然后继续执行;它不会等待 lambda 完成。
    猜你喜欢
    • 1970-01-01
    • 2020-08-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-03
    • 2021-08-03
    相关资源
    最近更新 更多