我正在更新一个库,该库具有一个在.NET3.5中构建的API图面.因此,所有方法都是同步的.我不能更改API(即,将返回值转换为Task),因为这将需要更改所有调用方.因此,留给我的是如何以同步的方式最好地调用异步方法.这是在ASP.NET 4、ASP.NET Core和.NET/.NET Core控制台应用程序的上下文中进行的.

我可能还不够清楚-情况是我有不支持异步的现有代码,我想使用仅支持异步方法的新库,如System.Net.Http和AWS SDK.因此,我需要弥合这一鸿沟,并且能够拥有可以同步调用但随后可以在其他地方调用异步方法的代码.

我做了很多阅读,这个问题被问了很多次,也回答了很多次.

Calling async method from non async method

Synchronously waiting for an async operation, and why does Wait() freeze the program here

Calling an async method from a synchronous method

How would I run an async Task<T> method synchronously?

Calling async method synchronously

How to call asynchronous method from synchronous method in C#?

问题是大多数答案都不一样!我见过的最常见的方法是使用.结果,但这可能会导致死锁.我已经try 了以下所有方法,它们都很有效,但我不确定哪种方法是避免死锁、具有良好性能并能很好地配合运行时的最佳方法(在尊重任务调度器、任务创建选项等方面).有明确的答案吗?最好的方法是什么?

private static T taskSyncRunner<T>(Func<Task<T>> task)
    {
        T result;
        // approach 1
        result = Task.Run(async () => await task()).ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 2
        result = Task.Run(task).ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 3
        result = task().ConfigureAwait(false).GetAwaiter().GetResult();

        // approach 4
        result = Task.Run(task).Result;

        // approach 5
        result = Task.Run(task).GetAwaiter().GetResult();


        // approach 6
        var t = task();
        t.RunSynchronously();
        result = t.Result;

        // approach 7
        var t1 = task();
        Task.WaitAll(t1);
        result = t1.Result;

        // approach 8?

        return result;
    }

推荐答案

所以我只剩下如何以同步方式最好地调用异步方法.

首先,这是一件可以做的事.我之所以声明这一点,是因为在Stack Overflow上,经常有人指出这是魔鬼的行为,作为笼统的声明,而不考虑具体的情况.

它不需要一直异步.阻止某个异步的东西使其同步可能会带来性能成本,这可能很重要,也可能完全无关.这取决于具体情况.

死锁来自两个试图同时进入同一个单线程同步上下文的线程.任何能够可靠避免这种情况的技术都可以避免阻塞造成的死锁.

在您的代码片段中,所有对.ConfigureAwait(false)的调用都是毫无意义的,因为不等待返回值.ConfigureAwait返回一个 struct ,在等待时,它会显示您请求的行为.如果简单地删除该 struct ,它将什么也不做.

RunSynchronously无效,因为并非所有任务都可以通过这种方式处理.这种方法适用于基于CPU的任务,在某些情况下可能无法工作.

.GetAwaiter().GetResult()Result/Wait()的不同之处在于它模拟了await异常传播行为.你需要决定你是想要还是不想要.(因此,研究一下这种行为是什么;不需要在这里重复.)如果您的任务包含单个异常,则await错误行为通常很方便,而且几乎没有什么负面影响.如果存在多个异常,例如来自多个任务失败的失败Parallel循环,则await将丢弃除第一个异常之外的所有异常.这使得调试变得更加困难.

所有这些方法都有相似的性能.他们会以这样或那样的方式分配一个操作系统事件并阻止它.这是最昂贵的部分.与那台机器相比,另一台机器相当便宜.我不知道哪种方法绝对是最便宜的.

如果抛出异常,这将是最昂贵的部分.在…上NET 5中,在一个快速的CPU上,异常处理的速度最多为每秒200000次.深层堆栈的速度较慢,任务机器往往会将异常重新抛出,使其成本成倍增加.有一些方法可以阻止任务,而不会重新调用异常,例如task.ContinueWith(_ => { }, TaskContinuationOptions.ExecuteSynchronously).Wait();.

我个人喜欢Task.Run(() => DoSomethingAsync()).Wait();模式,因为它绝对避免了死锁,它很简单,并且没有隐藏GetResult()可能隐藏的一些异常.但你也可以用GetResult().

Asp.net相关问答推荐

如何在编译时为我的 ASP.NET 项目中的每个控制器生成一个单独的 OpenAPI Swagger.json 文件?

如何在 ASP.Net Core 中验证上传的文件

系统日期时间?与 System.DateTime

创建一个供 ASP.NET 应用程序中的所有线程使用的静态 Regex 对象是否有效?

Dotnet core 2.0 身份验证多模式身份 cookie 和 jwt

获取没有主机的 url 部分

ASP.NET 平台是否有与 Heroku 等价的工具?

SameSite 属性如何自动添加到我的 Asp.net_SessionID cookie 中?

如何判断一个IP地址是否是私有的?

对布尔查询字符串参数使用true或1

使用 Lucene.NET 索引 .PDF、.XLS、.DOC、.PPT

上传时验证大文件

每个 'HttpRequest' 在 ASP.NET 中都有自己的线程吗?

使用 jQuery 调用 ASP.NET PageMethod/WebMethod - 返回整个页面

显示 ModalPopupExtender 时如何指定要运行的 javascript

对 ASP.NET 2.0 网页进行单元测试的最佳方法是什么?

如何在 ASP.NET 中设置下拉列表项?

如何将参数传递给 ASP.NET MVC 2 中的自定义 ActionFilter?

将 C# ASP.NET 数组传递给 Javascript 数组

在控制器 asp.net-core 中获取当前区域性