async方法fn直接.result会堵塞无法结束的问题

 碰到过这个一个问题,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的onClick方法根本没有结束,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()串起来才能实现。

转载:async和await这些玩意的入门

http://blog.stephencleary.com/2012/02/async-and-await.html

严格来说,这篇文章不是专门讲TAP入门的,而是讲微软是如何引入了
async和await关键字来实现程序组织的新模式,也就是编译器通过awaiter把你的代码从新‘’异步‘’组织了一下。然后系统级别支持这种async方法的caller有桌面UI, asp.net的request handler线程等(它们都有SynchronizationContext, 用于后台处理完这个async方法后回到原caller线程)。
async方法一般返回Task或Task<T>, 或者确定不用被上一层await时,也可以返回void, 没有return, 这和返回Task是一样的。
至于Task的实现,还是看源代码比较好。

一段精巧的代码

internal void EnsureThreadRequested()
{
    int num;
    for (int i = this.numOutstandingThreadRequests; i < ThreadPoolGlobals.processorCount; i = num)
    {
        num = Interlocked.CompareExchange(ref this.numOutstandingThreadRequests, i + 1, i);
        if (num == i)
        {
            ThreadPool.AdjustThreadsInPool(1u);
            return;
        }
    }
}

ThreadPoolWorkQueue类里的,没用Lock就实现了多线程情况下确保+1操作的一次调用。厉害。