c# - Async Task.WhenAll with timeout
.net async-ctp (10)
Además de la respuesta de svick, lo siguiente me funciona cuando tengo que esperar un par de tareas pero tengo que procesar otra cosa mientras estoy esperando:
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
}
¿Hay alguna forma en la nueva biblioteca de async dotnet 4.5 para establecer un tiempo de espera en el método Task.WhenAll
? Quiero buscar varias fuentes y detenerme luego de 5 segundos y omitir las fuentes que no se terminaron.
Además del tiempo de espera, también verifico la cancelación, lo cual es útil si está creando una aplicación web.
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;
}
}
Consulte las secciones "Rescate anticipado" y "Retención de tareas" de la Descripción general de patrones asíncronos basados en tareas de Microsoft.
Rescate temprano Una operación representada por t1 se puede agrupar en un WhenAny con otra tarea t2, y podemos esperar en la tarea WhenAny. t2 podría representar un tiempo de espera, una cancelación o alguna otra señal que hará que la tarea WhenAny se complete antes de que se complete t1.
Echa un vistazo a un combinador de tareas personalizado propuesto en 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
}
Aun no lo he intentado.
Llegué a la siguiente pieza de código que hace lo que necesitaba:
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;
}
}
}
Mi explicación está en mi blogpost: http://blog.bekijkhet.com/2012/03/c-async-examples-whenall-whenany.html
Lo que describe parece una demanda muy común, sin embargo, no pude encontrar un ejemplo de esto en ninguna parte. Y busqué mucho ... finalmente creé lo siguiente:
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();
Asumo aquí un método SomeTaskAsync que devuelve la Tarea <MyResult>.
De los miembros de las tareas completadas, solo las tareas de tipo MyResult son nuestras propias tareas que lograron ganarle al reloj. Task.Delay devuelve un tipo diferente. Esto requiere un poco de compromiso con la escritura, pero aún funciona muy bien y es bastante simple.
(Por supuesto, la matriz puede construirse dinámicamente usando una consulta + ToArray).
- Tenga en cuenta que esta implementación no requiere que SomeTaskAsync reciba un token de cancelación.
Parece que la sobrecarga de Task.WaitAll con el parámetro de tiempo de espera es todo lo que necesita; si se vuelve verdadero, entonces sabe que todos se completaron; de lo contrario, puede filtrar en IsCompleted.
if (Task.WaitAll(tasks, myTimeout) == false)
{
tasks = tasks.Where(t => t.IsCompleted);
}
...
Pienso que una opción más clara y robusta que también hace un correcto Task.WhenAny
excepciones sería utilizar Task.WhenAny
en cada tarea junto con una tarea de tiempo de espera , pasar por todas las tareas completadas y filtrar las tareas de tiempo de espera, y usar await Task.WhenAll()
lugar de Task.Result
para reunir todos los resultados.
Aquí hay una solución de trabajo completa:
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);
}
Puede combinar la Task
resultante con un Task.Delay()
usando Task.WhenAny()
:
await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(timeout));
Si desea cosechar tareas completadas en caso de un tiempo de espera:
var completedResults =
tasks
.Where(t => t.Status == TaskStatus.RanToCompletion)
.Select(t => t.Result)
.ToList();
Versión de resultado nulo de la respuesta de @ i3arnon, junto con comentarios y cambio de primer argumento para usar esta extensión.
También tengo un método de reenvío que especifica el tiempo de espera como un int que usa TimeSpan.FromMilliseconds(millisecondsTimeout)
para coincidir con otros métodos de Tarea.
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);
}