C# 异步 Task.WhenAll 超时
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9846615/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Async Task.WhenAll with timeout
提问by broersa
Is there a way in the new async dotnet 4.5 library to set a timeout on the Task.WhenAllmethod. I want to fetch several sources and stop after say 5 seconds and skip the sources that weren't finished.
在新的 async dotnet 4.5 库中有没有办法在Task.WhenAll方法上设置超时。我想获取多个源并在 5 秒后停止并跳过未完成的源。
采纳答案by svick
You could combine the resulting Taskwith a Task.Delay()using Task.WhenAny():
您可以将结果Task与Task.Delay()using结合起来Task.WhenAny():
await Task.WhenAny(Task.WhenAll(tasks), Task.Delay(timeout));
If you want to harvest completed tasks in case of a timeout:
如果您想在超时的情况下收获已完成的任务:
var completedResults =
tasks
.Where(t => t.Status == TaskStatus.RanToCompletion)
.Select(t => t.Result)
.ToList();
回答by David Peden
Check out the "Early Bailout" and "Task.Delay" sections from Microsoft's Task-Based Asynchronous Pattern Overview.
查看 Microsoft 的基于任务的异步模式概述中的“早期救助”和“Task.Delay”部分。
Early bailout. An operation represented by t1 can be grouped in a WhenAny with another task t2, and we can wait on the WhenAny task. t2 could represent a timeout, or cancellation, or some other signal that will cause the WhenAny task to complete prior to t1 completing.
提前救助。由 t1 表示的操作可以与另一个任务 t2 组合在一个 WhenAny 中,我们可以等待 WhenAny 任务。t2 可以表示超时、取消或某些其他信号,这些信号将导致 WhenAny 任务在 t1 完成之前完成。
回答by James Manning
Seems like the Task.WaitAll overload with the timeout parameter is all you need - if it returns true, then you know they all completed - otherwise, you can filter on IsCompleted.
似乎带有超时参数的 Task.WaitAll 重载就是您所需要的 - 如果它返回 true,那么您就知道它们都已完成 - 否则,您可以过滤 IsCompleted。
if (Task.WaitAll(tasks, myTimeout) == false)
{
tasks = tasks.Where(t => t.IsCompleted);
}
...
回答by broersa
I came to the following piece of code that does what I needed:
我来到了下面的一段代码,它可以满足我的需要:
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;
}
}
}
My explanation is in my blogpost: http://blog.bekijkhet.com/2012/03/c-async-examples-whenall-whenany.html
我的解释在我的博文中:http: //blog.bekijkhet.com/2012/03/c-async-examples-whenall-whenany.html
回答by Maxim Eliseev
Check out a custom task combinator proposed in http://tutorials.csharp-online.net/Task_Combinators
查看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
}
I have not tried it yet.
我还没有尝试过。
回答by Simon Mattes
In addition to svick's answer, the following works for me when I have to wait for a couple of tasks to complete but have to process something else while I'm waiting:
除了 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
}
回答by Erez Cohen
What you describe seems like a very common demand however I could not find anywhere an example of this. And I searched a lot... I finally created the following:
你所描述的似乎是一个非常普遍的需求,但是我在任何地方都找不到这样的例子。我搜索了很多......我最终创建了以下内容:
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();
I assume here a method SomeTaskAsync that returns Task<MyResult>.
我在这里假设有一个返回 Task<MyResult> 的方法 SomeTaskAsync。
From the members of completedTasks, only tasks of type MyResult are our own tasks that managed to beat the clock. Task.Delay returns a different type. This requires some compromise on typing, but still works beautifully and quite simple.
在 CompletedTasks 的成员中,只有 MyResult 类型的任务是我们自己设法赶时间的任务。Task.Delay 返回不同的类型。这需要在打字上做出一些妥协,但仍然可以很好地工作并且非常简单。
(The array can of course be built dynamically using a query + ToArray).
(当然可以使用查询 + ToArray 动态构建数组)。
- Note that this implementation does not require SomeTaskAsync to receive a cancellation token.
- 请注意,此实现不需要 SomeTaskAsync 来接收取消令牌。
回答by i3arnon
I think a clearer, more robust option that also does exception handling rightwould be to use Task.WhenAnyon each task together with a timeout task, go through all the completed tasks and filter out the timeout ones, and use await Task.WhenAll()instead of Task.Resultto gather all the results.
我认为一个更清晰、更健壮的选项也可以正确处理异常,是将Task.WhenAny每个任务与超时任务一起使用,遍历所有已完成的任务并过滤掉超时任务,并使用await Task.WhenAll()而不是Task.Result收集所有结果。
Here's a complete working solution:
这是一个完整的工作解决方案:
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);
}
回答by Tony
In addition to timeout, I also check the cancellation which is useful if you are building a web app.
除了超时,我还会检查取消,如果您正在构建 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;
}
}
回答by kjhf
void result version of @i3arnon 's answer, along with comments and changing first argument to use extension this.
@i3arnon 答案的无效结果版本,以及注释和更改第一个参数以使用扩展名 this。
I've also got a forwarding method specifying timeout as an int using TimeSpan.FromMilliseconds(millisecondsTimeout)to match other Task methods.
我还有一个转发方法,将超时指定为 intTimeSpan.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);
}

