让Task支持带超时的非阻塞异步等待
前言
大家都知道Task表示一个异步任务。如果我们想等待一个Task完成,有很多自带的实例、静态方法供我们选择。有的阻塞,有的不阻塞。不过带超时的等待只有一个,而且它是堵塞的。
这次给大家写个非阻塞的带超时的等待方法~ # Task已有的等待方法
Task实例已有的等待方法就是Wait
: ▲ 五个重载,一个无限等待,一个支持取消,两个支持超时(毫秒和
TimeSpan
),一个既支持取消也支持超时
但Task实例的等待方法的所有重载都有一个弊端,那就是阻塞。如果真的用这个方法来等待这个Task,那么一定会堵塞一个线程。所以通常不建议这样直接等待。
另外,Task还提供了静态的等待方法: ▲ Task静态的等待方法
Task.WaitAll
和Task.WaitAny
的功能基本和Task实例的Wait
方法是一样的,只是可以等待多个Task的实例罢了。
而Task.WhenAll
和WhenAny
才是不阻塞线程的异步等待。
可是!可以看上图,只有Task.Wait
系列的重载才有timout参数,也就是超时功能,而Task.When
系列则没有。
这也就是本文要讨论和解决的问题。如何让一个Task的等待既可以有超时功能,又是异步的不堵塞线程呢?
带超时的异步非阻塞等待方法的实现
Task有一个Task.Delay
静态方法,它是用来创建一个在指定时间延迟后完成的任务,类似于一个异步的Thread.Sleep
。而我们就可以用这个方法来间接实现异步非阻塞的等待。
Task.WhenAny
可以异步的等待多个任务中任意一个任务的完成。这样就可以和Task.Delay
做一个结合。思路就是要么是真正在执行的任务先完成,要么就是超时先完成。于是我们可以用Task.Delay
来创建一个新的Task,来比较两个Task的执行先后:
1 | public static async Task<TResult> WaitAsync<TResult>(Task<TResult> task, TimeSpan timeout) |
但这个扩展封装还不够好,如果我们的任务早已完成,但timeout设置的过长,那么Task.Delay
创建的延时任务会一直等到timeout设置的时间结束后才结束,会一直常驻后台,占用资源。
还好延时任务可以被取消,于是我们可以用CancellationTokenSource,把无用的延时任务给释放掉。
然后就有了以下完整的封装:
1 | namespace AmazingPP |
这样我们就可以在任意的Task实例上调用myTask.WaitAsync
来获取带超时的非阻塞异步等待了~~
参考资料 - [c# - Asynchronously wait for Task to complete with timeout - Stack Overflow](https://stackoverflow.com/q/4238345/6233938)