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<T>(Func<Task<T>> func) {
            var tcs = new TaskCompletionSource<T>();
            Task.Run(async () => {
                T data = await func().ConfigureAwait(false);
                tcs.SetResult(data);
            });
            return tcs.Task.Result;
        }

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

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

2023-10-10模拟测试,WinForm代码

        public static T Result<T>(Func<Task<T>> func) {
            var tcs = new TaskCompletionSource<T>();
            Task.Run(async () => {
                T data = await func().ConfigureAwait(false);
                tcs.SetResult(data);
            });
            return tcs.Task.Result;
        }
        private void BtnTest2_Click(object sender, EventArgs e) {
            //MessageBox.Show(CCC().Result);  //死锁
            MessageBox.Show(Result(CCC)); //无法更改底层,使用TaskCompletionSource解决死锁
        }
        /// <summary>
        /// 模拟层层调用
        /// </summary> 
        async Task<string> CCC() {
            return await BBB().ConfigureAwait(false); //其他层级都有ConfigureAwait(false)
        }
        /// <summary>
        /// 模拟层层调用
        /// </summary> 
        async Task<string> BBB() {
            return await AAA().ConfigureAwait(false);
        }
        async Task<string> AAA() {
            await Task.Delay(100); //.ConfigureAwait(false);  //设定这里缺少ConfigureAwait(false) ,如果不缺少,外部直接Result就不会死锁了
            return "执行";
        }

 

posted @ 2022-03-02 12:05  WmW  阅读(319)  评论(2编辑  收藏  举报