【问题标题】:release the calling thread while calling async function in synchronous context在同步上下文中调用异步函数时释放调用线程
【发布时间】:2020-06-16 15:22:02
【问题描述】:

在c#(dotnet core 3.1)中似乎不可能在不阻塞原始线程的情况下从非异步方法调用异步函数。为什么?

代码示例:

public async Task myMethodAsync() {
  await Task.Delay(5000);
}

public void callingMethhod() {
  myMethodAsync().Wait(); // all flavours of this expression, like f.ex. .Result seem to be blocking the calling thread
}

在异步方法完成之前释放调用线程,然后从那里继续执行的技术限制是什么?这在技术上是不可能的吗?

【问题讨论】:

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


    【解决方案1】:

    是的,这是意料之中的。这就是为什么存在Task 等概念的原因。如果可以做你想做的事:他们不需要——我们可以挥动一根魔杖,一切都将是异步的。等待类型的全部意义在于释放调用线程同时允许稍后继续是困难,并且需要调用代码的协调和参与;以及从什么叫它;等等 - 一直到堆栈到任何正在运行的线程。您的 callingMethhod 代码是同步的:它只能做一些事情:

    • 它可以运行到完成 - 意思是:它必须阻塞
    • 它可以抛出异常

    这样做的影响是async/await具有传染性;任何触及可等待对象的东西有点需要也是可等待的,这意味着:您通常只会从返回 callingMethhodmyMethodAsync 调用 [Value]Task[<T>],并且大概是带有 asyncawait(尽管在所示示例中这些位并不是绝对必要的)。

    【讨论】:

    • 我很想知道为什么会这样。 IE。为什么一定要这样,为什么允许继续“很难”?
    • @JKJ 简单地说:那会是什么样子?实际上会发生什么?如果异步事物不完整,调用堆栈接下来会去哪里? myMethodAsync 的结果会发生什么(这意味着返回值还是异常)?打电话给callingMethhod 的人是否也知道事情正在展开?如果是这样:如何?它只能看到void - 它对任务一无所知。在调用语义方面:在同步流中间等待只是......无法完成;你可以阻止(通过.Wait().Result),但你真的不应该(这显然是一个非常非常糟糕的主意)
    • @JKJ 将其放入上下文中 - 它可能有助于说明 异步有多复杂 - 这里与您的代码类似,但有两个 async 方法;现在查看右侧窗格,它显示了编译器为使其工作所做的工作 - 它非常非常复杂,因为调度异步延续本质上是一件复杂的事情:sharplab.io/…
    • @JKJ 隐藏线程状态非常困难 - 您需要了解 很多 调用语义 - 而且您不能只是中断线程,假装数据,然后说“你已经完成了,现在你正在做其他事情”。存储和展开线程状态是等待者启用的 - 再次:这就是这个概念存在的原因。没有那种 API;这不是你可以神奇地做到的事情。
    • @JKJ 还请记住:启用 await 所需的机制不是免费的 - 只说“会大量对性能有害”现在一切都被编译器神奇地异步了;如果你返回 void,你现在返回 ValueTask,等等”;它也不会是二进制兼容的——如果你明白我的意思,它会破坏世界。这甚至是可能:在async 方法中,有些事情你不能做,因为无法存储状态 - 任何涉及 ref-locals 的事情,例如 -或者因为线程语义 - 任何使用 lock 的东西,例如
    【解决方案2】:

    在c#(dotnet core 3.1)中似乎不可能在不阻塞原始线程的情况下从非异步方法调用异步函数。为什么?

    因为这就是同步的意思。同步意味着它阻塞线程直到方法完成。

    在异步方法完成之前释放调用线程,然后从那里继续执行的技术限制是什么?这在技术上是不可能的吗?

    如果你的调用方法想要释放它的线程,那么让它异步。异步方法能够释放它们的调用线程。这里没有什么是不可能的; asyncawait已经解决了。

    现在,如果您问“为什么不能每个方法都隐含为 async”,那么理论上这是可能的,但它会导致几个主要问题。出于向后兼容性的原因,它永远不能在 C# 中完成。立即想到的两个问题是:

    1. 有限互操作。不可能使用“一切皆为async”的语言与任何使用老式线程(Win32 互斥锁等)的东西进行互操作。
    2. 意外并发。有很多很多场景开发人员会假设同步代码。如果每个方法都可能是异步的,那么即使像var x = this._list[0]; this._list.RemoveAt(0); 这样简单的代码也不再安全。

    【讨论】:

    • 欣赏这一点。在我看来,通过不允许意外的异步性来保证同步性,在这种情况下可以被视为更多的理想特性,而不是技术限制。
    猜你喜欢
    • 2012-07-25
    • 1970-01-01
    • 2013-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-19
    • 2020-10-13
    • 2012-02-25
    相关资源
    最近更新 更多