将同步 API 包装到 Async 方法中
Posted
技术标签:
【中文标题】将同步 API 包装到 Async 方法中【英文标题】:Wraping sync API into Async method 【发布时间】:2014-02-01 08:24:02 【问题描述】:我的 MVC 应用程序使用一个库,并且该库的一些方法在内部调用 WCF 服务。从此 DLL 公开的所有方法都是同步的(它们都不返回 Task 或 Task),并且由于我们不拥有该程序集,因此无法将它们转换为 Async API。
但是,由于这些方法调用 WCF 服务,它们是网络绑定的(因此理想情况下它们应该是异步的)。
我想在我的 MVC 应用程序中使用异步控制器操作以使其更具可扩展性。我的问题是当一种方法本质上是同步的时,如何使整个方法管道可以等待。
异步操作 --> 等待异步方法 --> 等待异步方法 2 --> 从库中同步方法?
我应该使用 TaskCompletionSource 还是 Task.FromResult 来包装库方法调用?
另外,如果我使用上述方法,我的代码会比同步版本更具可扩展性吗?
【问题讨论】:
在异步中包装同步方法对可扩展性没有任何帮助。它只是将执行移到另一个线程上。如果该线程有重要的事情要做(如 UI 线程),这可能会提高响应能力。参见 Steven Toub 的帖子:blogs.msdn.com/b/pfxteam/archive/2012/03/24/10287244.aspx 【参考方案1】:我的问题是当一种方法本质上是同步时,如何使整个方法管道可以等待。
你不能。唯一的解决办法就是重写dll。
我应该使用 TaskCompletionSource 还是 Task.FromResult 来包装库方法调用?
都没有。
另外,如果我使用上述方法,我的代码会比同步版本更具可扩展性吗?
没有。它将稍微较少可扩展。
【讨论】:
谢谢斯蒂芬。但是,我正在阅读您的一篇博客,您提到“有一种方法可以将您的方法堆栈部分转换为异步”,我认为我处于这种情况。知道同步 API 进行网络绑定调用,我无法抗拒自己以异步方式调用它,但由于它是服务器应用程序,Task.Run 是不行的。对于这种情况,.NET 似乎应该有一个更好的解决方案,因为堆栈中的一个方法是同步的,而我无法控制它,这使我无法从异步中获益。 :( 补充一下,根据我的知识(我通过阅读你的大部分博客了解到:)),不管在这个同步方法调用之前我有多少等待可用的方法,它是用包装的TCS ,最终请求线程将最终执行同步方法并被阻塞。这减少了从控制器操作开始创建整个调用堆栈等待的目的。It seems like .NET should have a better solution for this kind of scenario...
问题是:它将如何工作?
那么,将方法堆栈部分转换为异步的最佳选择应该是什么?另外,如果我可以使用 Task.WhenAll(task1, task2) 在异步 API 中包装同步方法(使用 Task.FromResult 或类似方法),那么它不会提供一些性能提升吗?
在 ASP.NET 中,最好的选择是直接调用同步方法。使用Task.FromResult
毫无意义 - 你只会造成更多的开销。您可以将同步方法包装在Task.Run
中以同时执行多项操作(使单个请求更快);但是,由于每个请求都使用 多个 线程,因此这很容易降低整个服务器的可扩展性。【参考方案2】:
TaskCompletionSource<T>
是一种创建傀儡任务的方法,它可以在你喜欢的任何时候完成,并且可以在你喜欢的任何时候让它出错。这意味着,这在您的情况下是理想的,因为您无法控制您尝试使用的 API 方法。以下示例将为您提供一个良好的开端。
public class HomeController : Controller
public async Task<ActionResult> Index()
ViewBag.Message = await ProcessRequest();
return View();
//TResult -> can be of any built-in or custom type that you should decide.
Task<TResult> ProcessRequest()
// Make a TaskCompletionSource so we can return a puppet Task
TaskCompletionSource<TResult> tcs = new TaskCompletionSource<TResult>();
// Call your sync API method
SyncAPI syncApi = new SyncAPI();
// Call api method and set the result or exception based on the output from the API //method.
tcs.SetResult(TResult);
// Return the puppet Task, which isn't completed yet
return tcs.Task;
【讨论】:
谢谢奈尔。这就是我目前实施的。但是,如果您注意到异步调用是如何执行的,您将意识到最终包装同步 API 调用的 puppet 方法将在请求线程上执行,并且不会释放它以接受更多用户请求。因此,无论您在其上方有多少可等待的方法,您的请求线程最终都会在同步 API 调用时被阻塞。如果我错了,请纠正我。您可能想要 Response.Write 当前线程管理的 Id 在您的代码中作为概念证明。以上是关于将同步 API 包装到 Async 方法中的主要内容,如果未能解决你的问题,请参考以下文章