Подтвердить что ты не робот

Async Task.WhenAll с таймаутом

Есть ли способ в новой библиотеке async dotnet 4.5 установить таймаут в методе Task.WhenAll. Я хочу получить несколько источников и остановиться после 5 секунд и пропустить источники, которые не были завершены.

4b9b3361

Ответ 1

Вы можете комбинировать полученный 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();

Ответ 2

Я думаю, что более ясный, более надежный вариант, который также делает правильную обработку исключений, Task.WhenAny бы в том, чтобы использовать Task.WhenAny для каждой задачи вместе с задачей тайм-аута, пройти все выполненные задачи и отфильтровать те из них, а также использовать 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);
}

Ответ 3

Ознакомьтесь с разделами "Early Bailout" и "Task.Delay" из Microsoft Обзор асинхронных шаблонов на основе задач.

Раннее спасение. Операция, представленная t1, может быть сгруппирована в WhenAny с другой задачей t2, и мы можем ждать задание WhenAny. t2 может представлять собой тайм-аут или отмену или какой-либо другой сигнал, который приведет к завершению задачи WhenAny до завершения t1.

Ответ 4

То, что вы описываете, похоже на очень распространенное требование, однако я не мог найти нигде пример этого. И я много искал... Наконец я создал следующее:

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();

Я предполагаю здесь метод SomeTaskAsync, который возвращает Task <MyResult> .

Из участников завершенных задач, только задачи типа MyResult являются нашими собственными задачами, которым удалось бить часы. Task.Delay возвращает другой тип. Это требует некоторого компромисса при наборе текста, но все же работает красиво и довольно просто.

(Разумеется, массив может быть построен динамически с использованием запроса + ToArray).

  • Обратите внимание, что эта реализация не требует, чтобы SomeTaskAsync получал токен отмены.

Ответ 5

В дополнение к таймауту я также проверяю отмену, которая полезна, если вы создаете веб-приложение.

public static async Task WhenAll(
    IEnumerable<Task> tasks, 
    int millisecondsTimeOut,
    CancellationToken cancellationToken)
{
    using(Task timeoutTask = Task.Delay(millisecondsTimeOut))
    using(Task cancellationMonitorTask = Task.Delay(-1, cancellationToken))
    {
        Task completedTask = await Task.WhenAny(
            Task.WhenAll(tasks), 
            timeoutTask, 
            cancellationMonitorTask
        );

        if (completedTask == timeoutTask)
        {
            throw new TimeoutException();
        }
        if (completedTask == cancellationMonitorTask)
        {
            throw new OperationCanceledException();
        }
        await completedTask;
    }
}

Ответ 6

аннулируйте результирующую версию ответа @i3arnon вместе с комментариями и изменением первого аргумента для использования расширения this.

У меня также есть метод переадресации, указывающий время ожидания как целое число с использованием TimeSpan.FromMilliseconds(millisecondsTimeout) для сопоставления с другими методами 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);
}

Ответ 7

Я пришел к следующему фрагменту кода, который делает то, что мне нужно:

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

Ответ 8

Ознакомьтесь с комбинацией пользовательских задач, предложенной в 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
}

Я еще не пробовал.

Ответ 9

В дополнение к ответу 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
}

Ответ 10

Вы можете использовать следующий код:

        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. Когда все задачи возвращают результат, тайм-аут не наступит, и будет задан ResultResult.

После этого мы получаем только выполненные задачи. Задачи, которые не были выполнены, не будут иметь результатов.

Ответ 11

Кажется, что Task.WaitAll перегружает с параметром тайм-аута все, что вам нужно - если он возвращает true, то вы знаете, что все они завершены - в противном случае вы можете фильтровать на IsCompleted.

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