Есть ли способ в новой асинхронной библиотеке dotnet 4.5 установить тайм-аут для метода Task.WhenAll
. Я хочу получить несколько источников и остановиться, скажем, через 5 секунд и пропустить источники, которые не были завершены.
Асинхронная задача Task.WhenAll с тайм-аутом
Ответы (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();
Я думаю, что более четкий и надежный вариант, который также выполняет правильную обработку исключений, заключается в использовании 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);
}
Task.WhenAll
выполняется для задач, которые возвращают завершенные задачи (т. е. результаты Task.WhenAny
s). Затем я фильтрую эти задачи с помощью предложения where. Наконец, я использую Task.WhenAll
для этих задач, чтобы извлечь их фактические результаты. Все эти задачи уже должны быть выполнены на данный момент.
- person i3arnon; 17.09.2016
Ознакомьтесь с разделами Early Bailout и Task.Delay на сайте Microsoft Использование асинхронного шаблона на основе задач.
Раннее спасение. Операция, представленная t1, может быть сгруппирована в WhenAny с другой задачей t2, и мы можем ждать задачи WhenAny. t2 может представлять тайм-аут, отмену или какой-либо другой сигнал, который приведет к завершению задачи WhenAny до завершения t1.
Ознакомьтесь с настраиваемым комбинатором задач, предложенным на странице 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
}
Я еще не пробовал.
То, что вы описываете, кажется очень распространенным требованием, однако я нигде не мог найти пример этого. И я много искал... Я наконец создал следующее:
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 получал токен отмены.
В дополнение к тайм-ауту я также проверяю отмену, что полезно, если вы создаете веб-приложение.
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;
}
}
пустая версия результата ответа @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);
}
Похоже, что перегрузка Task.WaitAll с параметром тайм-аута — это все, что вам нужно — если она возвращает true, то вы знаете, что все они завершены — в противном случае вы можете фильтровать по IsCompleted.
if (Task.WaitAll(tasks, myTimeout) == false)
{
tasks = tasks.Where(t => t.IsCompleted);
}
...
Task.WaitAll()
является блокирующим, поэтому не рекомендуется использовать его в C# 5, если можно этого избежать.
- person svick; 25.03.2012
Task
s или async
не так проста. Во-вторых, какое это имеет значение?
- person svick; 25.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
В дополнение к ответу 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
}
Вы можете использовать следующий код:
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.
После этого мы получаем только выполненные задачи. Невыполненные задачи не будут иметь результатов.