【问题标题】:Is 'async/await all the way up' really required [duplicate]真的需要'async/await all the way up'吗[重复]
【发布时间】:2020-08-02 13:51:59
【问题描述】:

我熟悉 async/await,它使用 async 函数在 await 上将线程返回到池中。 我可以在为另一个 API 请求返回线程的控制器方法中以及在任何第 3 方 API 调用(例如 AWS)中看到这样做的好处。

但是在整个调用堆栈中使用它有什么好处吗? 考虑这个伪代码示例:

public class MyController
    public async Task<IActionResult> MyFirstFunction()
    {
        var result = await _myHandler.MySecondFunction();
        ...
        return Ok();
    }
}

public class MyHandler
    public Task<bool> MySecondFunction()
    {
        ...
        return thirdPartyHandler.ThirdPartyFunction(object);
    }
}

public class ThirdPartyHandler
    public async Task<bool> ThirdPartyFunction(Object object)
    {
        return await thirdParty.ExternalFunctionAsync(object);
    }
}

这段代码不会实现与使“MySecondFunction”函数异步并等待ThirdPartyFunction() 函数调用相同的事情,而不必处理捕获上下文并将线程返回到池等的开销更少吗?

诚然 MyHandler 和 ThirdPartyHandler 有点多余,可以合并

【问题讨论】:

标签: .net async-await callstack


【解决方案1】:

我熟悉 async/await,使用 async 函数在 await 上将线程返回到池中。

这不完全是how async/await worksawait 只是返回给它的调用者。它不会立即将线程返回到线程池。在 ASP.NET 场景中,awaits 的行为就像正常返回一样,一直到控制器操作。当控制器操作执行await 时,它将返回到 ASP.NET 运行时,也就是实际将线程返回到线程池的地方。

要记住的另一件有用的事情是这段代码:

var result = await MyFunctionAsync();

大致相当于这段代码:

var task = MyFunctionAsync();
var result = await task;

换句话说,方法首先被同步调用。当MyFunctionAsync 碰到await 并需要让步时,它将返回一个未完成的任务。然后这个函数等待那个任务——如果它需要让步,它会返回一个不完整的任务给它的调用者等等。这里没有线程切换——只是在一个正常的调用堆栈上返回值。

所以,一路使用await 不会破坏你的表现。这就是它的设计用途。


public Task<bool> MySecondFunction()
{
  ...
  return thirdPartyHandler.ThirdPartyFunction(object);
}

这段代码会不会实现与使“MySecondFunction”函数异步并等待ThirdPartyFunction() 函数调用相同的事情,而不必处理捕获上下文并将线程返回到池等的开销更少?

可能不会。 Eliding async and await 有很多陷阱。除非该函数中的 ... 确实微不足道,否则您可能希望保留 asyncawait 关键字。开销很小。

【讨论】:

    【解决方案2】:

    在您的示例中,您在没有等待的情况下传递了任务。这是完全有效的,如果您可以返回任务,您不必等待它。

    一个常见的误解是,await 实际上是从线程池中获取一个线程来执行任务。它不是。等待任务只会导致编译器为您的方法创建一个状态机,以便线程可以退出堆栈并返回线程池。正在等待的任务决定了会发生什么。它可以是一个完全同步的任务,像平常一样简单地完成当前线程上的工作,也可以是另一个线程,或者它可以等待回调。

    所以在性能方面,如果你返回 await 并不重要,让编译器担心优化它。

    public string Result;
    public async Task Something()
    {
        Thread.Sleep(1000);
        Result = "Something(): Thread: " + Thread.CurrentThread.ManagedThreadId;
    }
    
    void Main()
    {
        Result = "Main(): Thread: " + Thread.CurrentThread.ManagedThreadId;
        Something();
        Console.WriteLine("Before: " + Result);
        Thread.Sleep(2000);
        Console.WriteLine("After: " + Result);
    }
    

    这里的结果是:

    之前:Something():线程:6 之后:Something():线程:6

    为了获得预期的结果,该方法必须拉入一个线程来完成工作:

    await Task.Run(() =>
    {
        Thread.Sleep(1000);
        Result = "Something(): Thread: " + Thread.CurrentThread.ManagedThreadId;
    });
    

    public Task Something()
    {
        return Task.Run(() =>
        {
            Thread.Sleep(1000);
            Result = "Something(): Thread: " + Thread.CurrentThread.ManagedThreadId;
        });
    }
    

    两者都返回:

    之前:Main():线程:6 之后:Something():线程:25

    【讨论】:

      【解决方案3】:

      是的,但不是字面意思。

      真正的规则是在调用链中没有同步而不是异步。

      除了considerations made by Stephen Cleary(由JohanP 链接)之外,适用于迭代器的相同用例。您可能想立即验证您的参数:

      public Task DoSomethingAsync(string param1, int param2)
      {
          // validate parameters and throw if invalid
      
          return DoSomethingAsyncImpl(param1, param2);
      
          static async Task DoSomethingAsyncImpl(string param1, int param2)
          {
              // do async stuff
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-09-04
        • 1970-01-01
        • 1970-01-01
        • 2016-05-30
        • 2020-06-06
        • 2017-10-26
        • 2021-08-09
        相关资源
        最近更新 更多