C# 通过TaskCompletionSource解决async/await死锁的问题


async/await 是个好东西

但是呢,使用async/await时,很多旧项目经常会出现最外层要同步调用async/await方法的问题

而当我们在asp.net程序和winform等具有UI线程的项目使用async/await的Result属性或者Wait()方法同步阻塞获取数据时

就会出现死锁,具体的原因网上一搜一大把,这里就不做具体的介绍了

大家一般通过给await函数添加.ConfigureAwait(false)来解决这个问题

但是如果咱们引用的第三方dll内部没有这个.ConfigureAwait(false)或者其他未知问题,那就悲剧了,即使你给你所有的await都加上.ConfigureAwait(false)也依然是死锁

这个时候就只能另想办法了,而通过TaskCompletionSource就能解决这个问题,虽然很绕,很诡异,但是至少是解决死锁了。。。

具体的思路是创建一个TaskCompletionSource泛型对象,泛型类型为async/await返回值类型,然后开启一个新的异步线程,在这个异步线程中执行async/await方法,并将await的结果赋值给TaskCompletionSource,最后在异步线程的外面,同步阻塞调用TaskCompletionSource对象Task.Result

这样就貌似就不会出现死锁了

public static T Result(Func> func) {
            var tcs = new TaskCompletionSource();
            Task.Run(async () => {
                T data = await func().ConfigureAwait(false);
                tcs.SetResult(data);
            });
            return tcs.Task.Result;
        }

具体是怎么解决的,我也不是很清楚,估计还是线程竞争的问题,咱们启用了一个新的异步线程,估计是避免了这种竞争,还需要好好研究下

还有就是要注意Task.Run中func函数的异常处理,由于是异步线程,外部是捕获不到Task.Run中的异常的,如果有什么未处理的异常,可能会影响系统的稳定性