如何加入同步和异步?

时间:2021-04-18 23:08:15

标签: c# asp.net async-await task-parallel-library

我知道,async/await 的问题是老生常谈,但是,如果有人帮助我解决以下问题,那就太好了:

先决条件: .网络框架。遗产。

  • 具有同步操作方法的 API 控制器。
    • action 方法调用一些私有方法,这些方法创建 ISomething 和,
    • 使用另一个类的 public void Run(ISomething input) 方法(类的实例是在控制器的构造函数中创建的),
    • 通过像 ISomething 这样的异步库将 _lib.Send(ISomething input) 对象发送到应用程序的另一部分。

由于 public void Run(ISomething input)同步,并且库的方法是异步,因此 public void Run(ISomething input) 的引擎盖下有一个适配器 {1}}。 整个画面是这样的:

// _lib.Send(ISomething input) returns Task here and performs IO operation

public void Run(ISomething input)
{
   RunAsync(() => _lib.Send(ISomething input));
}

private void RunAsync(Func<Task> input)
{
   var result = Task.Run(() =>
            {
                input.Invoke();
            }).ConfigureAwait(false);            
   result.GetAwaiter().GetResult();
}

我的问题是:

  1. 是否应该在 Task.Run() 中使用 async/await(如果可能,请解释)?
  2. 在上述条件下有没有更好的方法来包装 _lib.Send(ISomething input)? // 签名为:public Task Send(ISomething input);

1 个答案:

答案 0 :(得分:1)

<块引用>

如何加入同步和异步?

最好的答案是“你没有”。在这种情况下,由于 Run 依赖于异步 _lib.Send,那么 Run 应该更改为异步。然后调用 Run 的控制器操作应该更改为异步的。然后一切都是异步的,你不再有同步异步反模式。

然而,有时这个答案并不现实,因为更新遗留代码需要开发人员时间,而且通常服务器时间比开发人员时间便宜。 “go async all the way”的理想答案对于新代码是很好的建议,但对于遗留代码可能不现实。因此,旧版应用中有 a few hacks you can use to support both synchronous and asynchronous code。这里使用的 hack 是“线程池 hack”。

<块引用>

有没有更好的方法来包装_lib.Send(ISomething input)

黑客的问题之一是它们并不适用于所有场景。从同步代码调用异步代码没有完美的万能解决方案,而且也不可能完美的万能解决方案。如果您必须保留旧的同步异步反模式,那么您只需要接受您需要使用的 hack 的限制。

在这种情况下,线程池黑客有一个限制,即被包装的代码 (_lib.Send) 必须可以从裸线程池线程调用。由于这是一个旧的 ASP.NET 应用程序,这意味着代码不能依赖于 ASP.NET 上下文(包括 HttpContext.Current)。根据 _lib.Send 的名称和描述,我希望它不依赖于上下文并且可以从任意线程调用,因此我希望线程池 hack 应该适用于该代码。

同样,这是基于这样的理解,即使用 Task RunAsync(ISomething input) => _lib.Send(input); 会更好,并且可以完全避免使用 Task.Run 包装器。您甚至可以保留旧的 void Run 实现,并根据需要逐渐将其余代码转换为使用 RunAsync。这就是我推荐的。

<块引用>

是否应该在 Task.Run() 中使用 async/await(如果可能,请解释)?

not necessary in this case。关键字应该用在任何重要的代码中,但由于委托只是调用一个方法,因此不需要 IMO。