【问题标题】:Does C# await keyword cause the function call to block?C# await 关键字是否会导致函数调用阻塞?
【发布时间】:2020-05-08 07:55:00
【问题描述】:

我正在尝试了解 async 和 await 在 C# 中的工作原理。

考虑下面的两个 sn-ps:

var appIdTask = GetAppIdAsync();
var clientSecretTask = GetClientSecretAsync();
var appId = await appIdTask;
var clientSecret = await clientSecretTask;
Execute(appId, clientSecret);

var appId = await GetAppIdAsync();
var clientSecret = await GetClientSecretAsync();
Execute(appId, clientSecret);

这两个 sn-ps 有不同的含义。对吗?

第一个将并行进行 Get 调用,而第二个将串行调用?

据我了解,第一次调用的 await 关键字会阻止第二次调用的执行。

【问题讨论】:

  • 学习时,使用更清晰的术语可能会有所帮助。 await 不会“阻止”执行,但会“暂停”其方法。我更喜欢在第一个块中使用术语“并发”(一次不止一件事情)而不是“并行”(使用多线程的更具体的并发形式)。

标签: c# asynchronous async-await


【解决方案1】:

它不会“阻塞”传统意义上的“在当前状态下暂停当前线程,直到收到一些信号”(异步的主要目标之一是通过允许更有效地使用池线程来增加吞吐量, 通过让他们不是都闲着等待 IO), 但是是的: 如果方法报告它不完整, 执行 将被 await 暂停, 并恢复 (很可能在不同的共享线程)当异步结果可用时。

所以是的,语义上它具有不同时运行这两个东西的效果(注意这仅适用于第一个调用是真正异步的)。

请注意,许多 API 并不期望多个并发异步操作,并且在第一个示例中会有未定义的行为。

【讨论】:

  • 也许“阻止”这个词用错了。第一个会比第二个跑得快吗?
  • @ashwnacharya 这完全取决于被调用的方法以及它们的作用;正如我所说,他们在这里彻底失败是完全有效的——许多 API 不期望多个并发调用;如果它确实工作,它取决于它是如何实现的;它可能真正同时工作,可能提高性能在某些情况下,或者它可能最终基本上是“流水线” - 在这种情况下你可能 在某些情况下节省一些延迟成本。这完全是情景性的,我可以为每一种可能的结果想出情景。
  • 其中两个正在发出彼此独立的 HTTP get 请求@Marc
  • @ashwnacharya 这里没有人可以回答关于速度的问题,因为这涉及到代码之外的其他事情。如果这些函数允许,第一个代码可以并行运行 GetAppIdAsync 和 GetClientSecretAsync,第二个代码在 GetAppIdAsync 完成之前不会运行 GetClientSecretAsync。
  • @LucaCremonesi 确定! Socket, NetworkStream, FileStream, SqlConnection, ... 最终,未等待的异步操作在语义上类似于运行两个常规(非异步)操作来自不同线程同一个对象。
【解决方案2】:

关于async/awaits 的一个重要规则是带有async 修饰符的方法将同步运行,直到它到达第一个未完成的任务。

异步不是并行,而是异步。这不是基于方法是async 的事实,而是基于返回的Task状态

我邀请你看看this post, and its code example

【讨论】:

    【解决方案3】:

    所以在第一个sn-p中:

    
    var appIdTask = GetAppIdAsync(); // here we are starting the execution of GetAppId on another thread, no "blocking" the main one
    var clientSecretTask = GetClientSecretAsync(); // instantly after starting previous method, run GetClientSecrent on yet another thread
    // at this point there are 2 parallel executions on different threads happening
    var appId = await appIdTask; // wait until GetAppId has finished and assign the result to appId variable
    var clientSecret = await clientSecretTask; // wait until GetClientSecret has finished and assign the result to clientSecret variable
    Execute(appId, clientSecret);
    
    

    还有第二个sn-p:

    var appId = await GetAppIdAsync(); // start executing GetAppId on different thread (not blocking the main one), and return once it has been completed and assign the result to appId variable
    // at this point GetAppIdAsync is completed
    var clientSecret = await GetClientSecretAsync(); // start executing GetClientSecret on different thread (not blocking the main one), and return once it has been completed and assign the result to appId clientSecret 
    // at this point GetClientSecretAsync is completed
    Execute(appId, clientSecret);
    

    如果您需要并行运行多个调用,我建议使用Task.WhenAll

    【讨论】:

    • 你能举个例子吗?
    • public async Task<int> GetValue() => 42;.
    • “在不同的线程上发生”是非常非常误导;不幸的是,在描述任务、线程、异步等时,词语的选择通常至关重要
    • 如果您以“假设GetAppIdAsyncGetClientSecretAsync 中的每一个都启动在其他线程上运行的任务”之类的免责声明开始,那么您可以继续回答就好了。
    猜你喜欢
    • 1970-01-01
    • 2020-07-12
    • 1970-01-01
    • 2015-04-02
    • 1970-01-01
    • 1970-01-01
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多