我正在更新一个库,该库具有一个在.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 Core 创建一个控制器来使用 SendGrid 发送邮箱

无法加载文件或程序集.无效指针(HRESULT 异常:0x80004003 (E_POINTER))

如何从 JavaScript 调用 C# 函数?

如何将 viewbag 显示为 html?

指定的 CGI 应用程序遇到错误,服务器终止了进程

带有 Web 套接字的 SignalR

将 ListItem 的 ASP.NET 列表数据绑定到 DropDownList 问题

Web.config 导致被组策略阻止错误

使用 JavaScript 禁用 ASP.NET 验证器

是否有用于 Asp.net 标记的#IF DEBUG?

如何获取当前登录用户的角色列表

InvalidOperationException:无法为角色创建 DbSet,因为此类型未包含在上下文模型中

不同域的登录页面

使用 .NET 连接到 AS400

ASP.NET 5、EF 7 和 SQLite - SQLite 错误 1:没有这样的表:博客

GZIP 与 DEFLATE 压缩相比有什么优势?

错误处理(向客户端发送 ex.Message)

ASP.NET 核心,更改未经授权的默认重定向

IIS 是否执行非法字符替换?如果是这样,如何阻止它?

以编程方式滚动到锚标记