如何为 Task 添加超时功能(2):Task.WhenAny

前言

上次的《如何为 Task 添加超时功能》文章中,我们的实现使用了循环判断,等待任务执行完成,在性能上会有一定影响。

有网友留言,提供了一个更好的实现思路:.

如何为 Task 添加超时功能(2):Task.WhenAny

为什么说这种方法更好呢?

原理

官网上有一篇文章《使用 Async 和 Await 的异步编程》[1],介绍了如何高效地等待任务,其中有这样一段话:

另一种选择是使用 WhenAny,它将返回一个当其参数完成时才完成的 Task。你可以等待返回的任务,了解它已经完成了。以下代码展示了可以如何使用 WhenAny 等待第一个任务完成,然后再处理其结果。

var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
while (breakfastTasks.Count > 0)
{
    Task finishedTask = await Task.WhenAny(breakfastTasks);
    if (finishedTask == eggsTask)
    {
        Console.WriteLine("Eggs are ready");
    }
    else if (finishedTask == baconTask)
    {
        Console.WriteLine("Bacon is ready");
    }
    else if (finishedTask == toastTask)
    {
        Console.WriteLine("Toast is ready");
    }
    breakfastTasks.Remove(finishedTask);
}

也就是说,Task.WhenAny会返回其参数中第一个完成的任务,而无需循环判断。

根据以上描述,我们可以按如下方式修改超时功能:

  • 同时开启 2 个任务,一个原始 task,另一个是刚好在超时时间完成的 timeoutTask

  • 使用 WhenAny 等待

  • 检查返回值是否为 timeoutTask,如果是就表示原任务超时了

实现

修改后的 Timeout 扩展方法:

public static async Task<T> Timeout<T>(this Task<T> task, int milliseconds)
{
    var timeoutTask = Task.Delay(milliseconds);
    var allTasks = new List<Task> { task, timeoutTask };
    Task finishedTask = await Task.WhenAny(allTasks);
    if (finishedTask == timeoutTask)
    {
        return default(T);
    }

    return await task;
}

使用方式还是保持不变:

var data = await GetFromCache().Timeout(1000);

if (data is null)
{
    data = await GetFromDB();
}

结论

今天,我们通过使用 Task.WhenAny,为异步任务添加了超时功能。

在此感谢网友“受气de灰太狼”提供的解决思路!