碰到过这个一个问题,fn是一个aysnc方法,在spotfire程序的UI线程上经过多层方法调用到的时候如果是直接写var a = fn().result就把spotfire卡死了。result也拿不出来(看不到结果a)
这实际是一个没有正确使用异步方法的问题。异步方法虽然可以同步调用(.result),但如果是UI线程里这么干,就出事了。async就要await调用,如果当前方法不是异步的(比如上层的接口定下来了,不过好像加不加async不影响接口实现),那就只能多包一层线程来同步调用。
下面第一篇文章解释了await关键字实际干了什么事(即SynchronizationContext.Current.post,post到UI线程的消息队列里),以及.result干了什么事情(同步执行)
下面这篇文章解释了SynchronizationContext在异步编程中的作用,特别是和ExecutionContext的差异(ExecutionContext是内部的,一般不用管)
所以使用await关键字调用async方法不会有问题。因为UI线程没有阻塞,UI的消息队列还有机会处理Post来的http异步请求结束的消息。
但是fn.result会阻塞,因为是同步执行,所以UI的on Click方法根本没有结束,UI消息队列没有空来执行其它东西的能力(http异步请求的东西取回来了却结束不了)。
所以Three ways out:
1.使用await调用aysnc, 这是最优方案,因为UI click事件执行完全不需要等待http请求,消息队列没有空转着等待。
2. ConfigureAwait(false),这个算是workaround,UI click事件还是等http请求结束了才全部结束,而且中间还要利用一个别的线程来执行http请求结束存放变量的操作,然后再回到UI线程来结束整个click.这个过程中消息队列在白白等待着。
3.上面的task.run( ()=>fn()).result方法再起一个线程来启动fn,在不能改Library,又不能用await的情况下也是一种写法。
之所以建议Library中总是ConfigureAwait(false)就是怕有用户在使用当前Task或上层Task(不一定是这个Task(Awaitable)的直接调用)时,用.Result的方式”同步地使用异步方法”,而没有使用await(不是所有的上层调用都像UI的button click方法private async void Button_Click()都支持async.
关于async和Task的使用可以参考:
实际上async方法基本都需要返回一个Task,上述的async void Button_Click()是一个不好的pattern, “fire-and-forget”机制, caller没有办法了解这个方法执行完成的情况或者抛出的错误。只有在最上层的UI event等系统方法才这么写。
我觉得其实是微软的UI Event代码带坏了大家,完全可以设计成必须要返回一个Task的机制,哪怕是Task<void>,也能保证后面的代码被suspend到这个task之后。如果自己写的代码是aysnc void,则因为没有GetAwaiter()(属于Task),则不能被await,结果就直接跑过去了,即使aysnc void方法里有下一层的await调用,因为上层没有等它执行完,这个await调用也不会suspend aysnc void方法后的代码执行,仅仅是系统看到await再开一个线程去跑而已。顺序关系需要GetAwaiter()串起来才能实现。