Async Task.WhenAll with timeout

时间:2012-03-23 21:19:58

标签: c# .net async-ctp async-await

在新的异步dotnet 4.5库中是否有办法在Task.WhenAll方法上设置超时。我想获取几个来源并在说出5秒之后停止并跳过未完成的来源。

11 个答案:

答案 0 :(得分:71)

您可以使用Task将结果Task.Delay()Task.WhenAny()合并:

await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(timeout));

如果您想在超时的情况下收获已完成的任务:

var completedResults =
  tasks
  .Where(t => t.Status == TaskStatus.RanToCompletion)
  .Select(t => t.Result)
  .ToList();

答案 1 :(得分:18)

我认为更清晰,更强大的选项does exception handling rightTask.WhenAnytimeout task一起使用{{3}},完成所有已完成的任务并过滤掉超时一个,并使用await Task.WhenAll()代替Task.Result来收集所有结果。

这是一个完整的解决方案:

static async Task<TResult[]> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks, TimeSpan timeout)
{
    var timeoutTask = Task.Delay(timeout).ContinueWith(_ => default(TResult));
    var completedTasks = 
        (await Task.WhenAll(tasks.Select(task => Task.WhenAny(task, timeoutTask)))).
        Where(task => task != timeoutTask);
    return await Task.WhenAll(completedTasks);
}

答案 2 :(得分:9)

查看微软Task-Based Asynchronous Pattern Overview的“早期救助”和“Task.Delay”部分。

  

早期救助。由t1表示的操作可以分组为a   WhenAny与另一个任务t2,我们可以等待WhenAny任务。 T2   可能代表超时,或取消,或其他一些信号   将导致在完成t1之前完成WhenAny任务。

答案 3 :(得分:2)

你所描述的似乎是一个非常普遍的需求,但我无法找到任何一个例子。我搜索了很多...我终于创建了以下内容:

TimeSpan timeout = TimeSpan.FromSeconds(5.0);

Task<Task>[] tasksOfTasks =
{
    Task.WhenAny(SomeTaskAsync("a"), Task.Delay(timeout)),
    Task.WhenAny(SomeTaskAsync("b"), Task.Delay(timeout)),
    Task.WhenAny(SomeTaskAsync("c"), Task.Delay(timeout))
};

Task[] completedTasks = await Task.WhenAll(tasksOfTasks);

List<MyResult> = completedTasks.OfType<Task<MyResult>>().Select(task => task.Result).ToList();

我在这里假设一个返回Task&lt; MyResult&gt;的SomeTaskAsync方法。

来自completedTasks的成员,只有MyResult类型的任务才是我们自己的任务,能够击败时钟。 Task.Delay返回不同的类型。 这需要在打字方面做出一些妥协,但仍然可以很好地工作,非常简单。

(当然可以使用查询+ ToArray动态构建数组。)

  • 请注意,此实现不需要SomeTaskAsync来接收取消令牌。

答案 4 :(得分:2)

除了超时,我还会检查取消,如果您正在构建网络应用程序,这将非常有用。

"^.+@[Aa][Bb][Cc][.][Cc][Oo][Mm]$"

答案 5 :(得分:1)

@ i3arnon答案的无效结果版本,以及注释和更改第一个参数以使用扩展名this。

我还有一个转发方法,它使用TimeSpan.FromMilliseconds(millisecondsTimeout)将超时指定为int值,以匹配其他Task方法。

public static async Task WhenAll(this IEnumerable<Task> tasks, TimeSpan timeout)
{
  // Create a timeout task.
  var timeoutTask = Task.Delay(timeout);

  // Get the completed tasks made up of...
  var completedTasks =
  (
    // ...all tasks specified
    await Task.WhenAll(tasks

    // Now finish when its task has finished or the timeout task finishes
    .Select(task => Task.WhenAny(task, timeoutTask)))
  )
  // ...but not the timeout task
  .Where(task => task != timeoutTask);

  // And wait for the internal WhenAll to complete.
  await Task.WhenAll(completedTasks);
}

答案 6 :(得分:0)

我来到了下面的代码片段,它可以满足我的需求:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Net.Http;
using System.Json;
using System.Threading;

namespace MyAsync
{
    class Program
    {
        static void Main(string[] args)
        {
            var cts = new CancellationTokenSource();
            Console.WriteLine("Start Main");
            List<Task<List<MyObject>>> listoftasks = new List<Task<List<MyObject>>>();
            listoftasks.Add(GetGoogle(cts));
            listoftasks.Add(GetTwitter(cts));
            listoftasks.Add(GetSleep(cts));
            listoftasks.Add(GetxSleep(cts));

            List<MyObject>[] arrayofanswers = Task.WhenAll(listoftasks).Result;
            List<MyObject> answer = new List<MyObject>();
            foreach (List<MyObject> answers in arrayofanswers)
            {
                answer.AddRange(answers);
            }
            foreach (MyObject o in answer)
            {
                Console.WriteLine("{0} - {1}", o.name, o.origin);
            }
            Console.WriteLine("Press <Enter>");
            Console.ReadLine();
        } 

        static async Task<List<MyObject>> GetGoogle(CancellationTokenSource cts) 
        {
            try
            {
                Console.WriteLine("Start GetGoogle");
                List<MyObject> l = new List<MyObject>();
                var client = new HttpClient();
                Task<HttpResponseMessage> awaitable = client.GetAsync("http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=broersa", cts.Token);
                HttpResponseMessage res = await awaitable;
                Console.WriteLine("After GetGoogle GetAsync");
                dynamic data = JsonValue.Parse(res.Content.ReadAsStringAsync().Result);
                Console.WriteLine("After GetGoogle ReadAsStringAsync");
                foreach (var r in data.responseData.results)
                {
                    l.Add(new MyObject() { name = r.titleNoFormatting, origin = "google" });
                }
                return l;
            }
            catch (TaskCanceledException)
            {
                return new List<MyObject>();
            }
        }

        static async Task<List<MyObject>> GetTwitter(CancellationTokenSource cts)
        {
            try
            {
                Console.WriteLine("Start GetTwitter");
                List<MyObject> l = new List<MyObject>();
                var client = new HttpClient();
                Task<HttpResponseMessage> awaitable = client.GetAsync("http://search.twitter.com/search.json?q=broersa&rpp=5&include_entities=true&result_type=mixed",cts.Token);
                HttpResponseMessage res = await awaitable;
                Console.WriteLine("After GetTwitter GetAsync");
                dynamic data = JsonValue.Parse(res.Content.ReadAsStringAsync().Result);
                Console.WriteLine("After GetTwitter ReadAsStringAsync");
                foreach (var r in data.results)
                {
                    l.Add(new MyObject() { name = r.text, origin = "twitter" });
                }
                return l;
            }
            catch (TaskCanceledException)
            {
                return new List<MyObject>();
            }
        }

        static async Task<List<MyObject>> GetSleep(CancellationTokenSource cts)
        {
            try
            {
                Console.WriteLine("Start GetSleep");
                List<MyObject> l = new List<MyObject>();
                await Task.Delay(5000,cts.Token);
                l.Add(new MyObject() { name = "Slept well", origin = "sleep" });
                return l;
            }
            catch (TaskCanceledException)
            {
                return new List<MyObject>();
            }

        } 

        static async Task<List<MyObject>> GetxSleep(CancellationTokenSource cts)
        {
            Console.WriteLine("Start GetxSleep");
            List<MyObject> l = new List<MyObject>();
            await Task.Delay(2000);
            cts.Cancel();
            l.Add(new MyObject() { name = "Slept short", origin = "xsleep" });
            return l;
        } 

    }
}

我的解释在我的博文中: http://blog.bekijkhet.com/2012/03/c-async-examples-whenall-whenany.html

答案 7 :(得分:0)

查看http://tutorials.csharp-online.net/Task_Combinators

中提出的自定义任务组合器
async static Task<TResult> WithTimeout<TResult> 
   (this Task<TResult> task, TimeSpan timeout)
 {
   Task winner = await (Task.WhenAny 
      (task, Task.Delay (timeout)));
   if (winner != task) throw new TimeoutException();
   return await task; // Unwrap result/re-throw
}

我还没有尝试过。

答案 8 :(得分:0)

除了svick的答案之外,当我必须等待完成几项任务但在等待时必须处理其他任务时,以下内容对我有用:

Task[] TasksToWaitFor = //Your tasks
TimeSpan Timeout = TimeSpan.FromSeconds( 30 );

while( true )
{
    await Task.WhenAny( Task.WhenAll( TasksToWaitFor ), Task.Delay( Timeout ) );
    if( TasksToWaitFor.All( a => a.IsCompleted ) )
        break;

    //Do something else here
}

答案 9 :(得分:0)

您可以使用以下代码:

        var timeoutTime = 10;

        var tasksResult = await Task.WhenAll(
                                listOfTasks.Select(x => Task.WhenAny(
                                    x, Task.Delay(TimeSpan.FromMinutes(timeoutTime)))
                                )
                            );


        var succeededtasksResponses = tasksResult
                                               .OfType<Task<MyResult>>()
                                               .Select(task => task.Result);

        if (succeededtasksResponses.Count() != listOfTasks.Count())
        {
            // Not all tasks were completed
            // Throw error or do whatever you want
        }

        //You can use the succeededtasksResponses that contains the list of successful responses

工作原理:

您需要将timeoutTime变量放入完成所有任务的时间限制。因此,基本上所有任务都将在您在timeoutTime中设置的最大时间内等待。当所有任务都返回结果时,将不会发生超时并且将设置taskResult。

在那之后,我们只得到完成的任务。未完成的任务将没有结果。

答案 10 :(得分:-1)

看起来像Task.WaitAll带有timeout参数的重载就是你所需要的 - 如果它返回true,那么你知道它们都已完成 - 否则,你可以过滤IsCompleted。

if (Task.WaitAll(tasks, myTimeout) == false)
{
    tasks = tasks.Where(t => t.IsCompleted);
}
...