.NET——让Task支持带超时的非阻塞异步等待

前言

大家都知道Task表示一个异步任务。如果我们想等待一个Task完成,有很多自带的实例、静态方法供我们选择。有的阻塞,有的不阻塞。不过带超时的等待只有一个,而且它是堵塞的。

这次给大家写个非阻塞的带超时的等待方法~

Task已有的等待方法

Task实例已有的等待方法就是Wait
image.png
▲ 五个重载,一个无限等待,一个支持取消,两个支持超时(毫秒和TimeSpan),一个既支持取消也支持超时

但Task实例的等待方法的所有重载都有一个弊端,那就是阻塞。如果真的用这个方法来等待这个Task,那么一定会堵塞一个线程。所以通常不建议这样直接等待。

另外,Task还提供了静态的等待方法:
image.png
▲ Task静态的等待方法

Task.WaitAllTask.WaitAny的功能基本和Task实例的Wait方法是一样的,只是可以等待多个Task的实例罢了。

Task.WhenAllWhenAny才是不阻塞线程的异步等待。

可是!可以看上图,只有Task.Wait系列的重载才有timout参数,也就是超时功能,而Task.When系列则没有。

这也就是本文要讨论和解决的问题。如何让一个Task的等待既可以有超时功能,又是异步的不堵塞线程呢?

带超时的异步非阻塞等待方法的实现

Task有一个Task.Delay静态方法,它是用来创建一个在指定时间延迟后完成的任务,类似于一个异步的Thread.Sleep。而我们就可以用这个方法来间接实现异步非阻塞的等待。

Task.WhenAny可以异步的等待多个任务中任意一个任务的完成。这样就可以和Task.Delay做一个结合。思路就是要么是真正在执行的任务先完成,要么就是超时先完成。于是我们可以用Task.Delay来创建一个新的Task,来比较两个Task的执行先后:

public static async Task<TResult> WaitAsync<TResult>(Task<TResult> task, TimeSpan timeout)
{
    if (await Task.WhenAny(task, Task.Delay(timeout)) == task)
    {
        return await task;
    }
    else
    {
        throw new TimeoutException("The operation has timed out.");
    }
}

但这个扩展封装还不够好,如果我们的任务早已完成,但timeout设置的过长,那么Task.Delay创建的延时任务会一直等到timeout设置的时间结束后才结束,会一直常驻后台,占用资源。

还好延时任务可以被取消,于是我们可以用CancellationTokenSource,把无用的延时任务给释放掉。

然后就有了以下完整的封装:

namespace AmazingPP
{
    public static class TaskWaitingExtensions
    {
        public static async Task WaitAsync(this Task task,TimeSpan timeout)
        {
            using (var timeoutCancellationTokenSource = new CancellationTokenSource())
            {
                var delayTask = Task.Delay(timeout, timeoutCancellationTokenSource.Token);
                if (await Task.WhenAny(task,delayTask) == task)
                {
                    timeoutCancellationTokenSource.Cancel();
                    await task;
                }
                else
                {
                    throw new TimeoutException("The opertion has timed out.");
                }
            }
        }

        public static async Task<TResult> WaitAsync<TResult>(this Task<TResult> task, TimeSpan timeout)
        {
            using (var timeoutCancellationTokenSource = new CancellationTokenSource())
            {
                var delayTask = Task.Delay(timeout, timeoutCancellationTokenSource.Token);
                if (await Task.WhenAny(task, delayTask) == task)
                {
                    timeoutCancellationTokenSource.Cancel();
                    return await task;
                }
                else
                {
                    throw new TimeoutException("The operation has timed out.");
                }
            }
        }
    }
}

这样我们就可以在任意的Task实例上调用myTask.WaitAsync来获取带超时的非阻塞异步等待了~~

参考资料

  • [c# - Asynchronously wait for Task to complete with timeout - Stack Overflow](https://stackoverflow.com/q/4238345/6233938)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值