Асинхронная задача Task.WhenAll с тайм-аутом

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


person broersa    schedule 23.03.2012    source источник


Ответы (11)


Вы можете объединить полученный 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();
person svick    schedule 23.03.2012
comment
Это имеет наибольшее количество голосов, но знаем ли мы, является ли теперь это допустимым подходом для достижения этой цели? - person TheJediCowboy; 13.07.2013
comment
@CitadelCSAlum Что ты имеешь в виду? Этот код делает то, что просят. Если вы мне не верите, вы можете прочитать документацию или попробовать сами. - person svick; 16.07.2013
comment
Хотя это принятый ответ, делает ли он именно то, что было описано в вопросе? Если я правильно понимаю, если таймаут происходит до выполнения всех задач, то никакого результата не получено (даже если часть задач была выполнена). Я прав? Я искал что-то, что позволит извлекать результаты из нескольких задач - беря только те, которые превзошли тайм-аут, независимо от того, не удалось ли это сделать остальным задачам. Смотрите мой ответ ниже. - person Erez Cohen; 01.09.2014
comment
@ErezCohen Ты прав. Думаю, я ответил в основном на заголовок вопроса, а не на его тело (особенно на то, что пропустил исходные тексты, которые не были закончены). - person svick; 01.09.2014
comment
WhenAny возвращает завершенную задачу. Чтобы определить, запущена ли задача задержки или WhenAll, вы также можете проверить результат await Task.WhenAny. - person Gertjan; 30.05.2016
comment
Он не извлекает завершенные источники в случае тайм-аута - person Menelaos Vergis; 16.09.2016
comment
@MenelaosVergis Вот для чего предназначена вторая часть ответа (добавленная @usr). - person svick; 16.09.2016
comment
Может ли кто-нибудь добавить более полный пример кода, объединяющий обе части фрагмента? В настоящее время неясно, как все это сочетается. - person James South; 10.07.2018
comment
@James South, два фрагмента можно объединить, просто вызвав второй после первого. Сначала ждешь, а потом собираешь результаты выполненных заданий. Возможно, что будут выполнены все задания, или только одинаковые, или ни одно из них. - person Theodor Zoulias; 28.03.2020

Я думаю, что более четкий и надежный вариант, который также выполняет правильную обработку исключений, заключается в использовании Task.WhenAny для каждой задачи вместе с < href="https://stackoverflow.com/a/11191070/885318">задача тайм-аута, просмотрите все завершенные задачи и отфильтруйте тайм-ауты и используйте 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);
}
person i3arnon    schedule 08.09.2014
comment
Есть два WhenAll, есть ли проблемы с производительностью? Второй WhenAll должен распаковать Task‹ ›? Не могли бы вы объяснить это? - person Menelaos Vergis; 16.09.2016
comment
@MenelaosVergis Первый Task.WhenAll выполняется для задач, которые возвращают завершенные задачи (т. е. результаты Task.WhenAnys). Затем я фильтрую эти задачи с помощью предложения where. Наконец, я использую Task.WhenAll для этих задач, чтобы извлечь их фактические результаты. Все эти задачи уже должны быть выполнены на данный момент. - person i3arnon; 17.09.2016

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

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

person David Peden    schedule 23.03.2012
comment
Вы хотите добавить краткое изложение того, что там написано? - person svick; 04.11.2012
comment
Не знаю, почему вы вернулись к этому сообщению, но ваш пример кода — это именно то, что описано в документе (как я полагаю, вы хорошо осведомлены). По вашей просьбе я обновил свой ответ дословной цитатой. - person David Peden; 05.11.2012
comment
@DavidPeden, эта ссылка теперь не работает, поиск Google выдал эту статью, не уверен, что это та, на которую вы ссылаетесь. docs.microsoft. com/en-us/dotnet/standard/ - person DynaWeb; 23.09.2020
comment
Спасибо. Я обновил ссылку, которая является третьей статьей в той же корневой документации, на которую вы ссылались. - person David Peden; 23.09.2020

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

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

person Maxim Eliseev    schedule 04.04.2014
comment
а) Ссылка битая. б) Это работает для одной задачи, о которой не спрашивал ОП. - person i3arnon; 09.09.2014

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

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›.

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

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

  • Обратите внимание, что эта реализация не требует, чтобы SomeTaskAsync получал токен отмены.
person Erez Cohen    schedule 31.08.2014
comment
Это похоже на то, что должно быть инкапсулировано во вспомогательный метод. - person svick; 01.09.2014
comment
@ErezCohen Я сделал свой ответ еще проще, если вы хотите взглянуть: stackoverflow.com/a/25733275/885318 - person i3arnon; 09.09.2014
comment
@I3arnon - Здорово!. Мне это нравится. - person Erez Cohen; 11.09.2014

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

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;
    }
}
person Tony    schedule 07.08.2015
comment
С этим кодом есть проблема: если какие-либо обычные задачи завершены, задача тайм-аута не ожидается.. поэтому ваш код будет выполняться до тех пор, пока задача тайм-аута не будет удалена, и если это произойдет до того, как задача тайм-аута будет выполнена до конца, вы получите Инвалидстатеоператион. Оставьте задачи, и все в порядке. - person Stephan Steiner; 05.11.2019

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

У меня также есть метод пересылки, указывающий тайм-аут как int, используя 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);
}
person Slate    schedule 18.10.2018

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

if (Task.WaitAll(tasks, myTimeout) == false)
{
    tasks = tasks.Where(t => t.IsCompleted);
}
...
person James Manning    schedule 24.03.2012
comment
Я думаю, что все эти задачи запущены в собственных потоках, а новые асинхронные функции — нет, но поправьте меня, если я ошибаюсь. Я только начинаю этот новый асинхронный материал. - person broersa; 25.03.2012
comment
Task.WaitAll() является блокирующим, поэтому не рекомендуется использовать его в C# 5, если можно этого избежать. - person svick; 25.03.2012
comment
@broersa Во-первых, я думаю, вы ошиблись, связь между потоками и методами Tasks или async не так проста. Во-вторых, какое это имеет значение? - person svick; 25.03.2012
comment
@svick Блокировка - это слово, которое я искал. Теперь все становится ясно. - person broersa; 26.03.2012

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

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

person broersa    schedule 26.03.2012

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

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

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

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

person Jorge Freitas    schedule 12.07.2019