【问题标题】:How to - Multiple Async tasks with timeout and cancellation如何 - 具有超时和取消的多个异步任务
【发布时间】:2014-11-01 04:40:03
【问题描述】:

我想在设置超时的同时触发几个任务。我们的想法是从不计其数的任务中收集结果,并取消(甚至忽略)其他任务。

我尝试按照 here 的说明使用扩展方法 WithCancellation,但是抛出异常导致 WhenAll 返回并且不提供任何结果。

这是我尝试过的,但我也向其他方向开放(但请注意,我需要使用 await 而不是 Task.Run,​​因为我需要任务中的 httpContext):

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));

IEnumerable<Task<MyResults>> tasks = 
            from url in urls
            select taskAsync(url).WithCancellation(cts.Token);

Task<MyResults>[] excutedTasks = null;

MyResults[] res = null;
try
{
    // Execute the query and start the searches:
    excutedTasks = tasks.ToArray();

    res = await Task.WhenAll(excutedTasks);
}
catch (Exception exc)
{
    if (excutedTasks != null)
    {
        foreach (Task<MyResults> faulted in excutedTasks.Where(t => t.IsFaulted))
        {
            // work with faulted and faulted.Exception
        }
    }
}

// work with res

编辑: 在下面@Servy 的回答之后,这是我使用的实现:

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));

IEnumerable<Task<MyResults>> tasks = 
            from url in urls
            select taskAsync(url).WithCancellation(cts.Token);

// Execute the query and start the searches:
Task<MyResults>[] excutedTasks = tasks.ToArray();

try
{
    await Task.WhenAll(excutedTasks);
}
    catch (OperationCanceledException)
{
    // Do nothing - we expect this if a timeout has occurred
}

IEnumerable<Task<MyResults>> completedTasks = excutedTasks.Where(t => t.Status == TaskStatus.RanToCompletion);

var results = new List<MyResults>();
completedTasks.ForEach(async t => results.Add(await t));

【问题讨论】:

  • taskAsync 是做什么的?
  • 在我的情况下,他们使用 httpClient 检索信息,但是我认为任何异步任务都可以(您在下面提供的任务应该可以)。关键是 taskAsync 没有收到取消令牌。

标签: c# asynchronous timeout task .net-4.5


【解决方案1】:

如果任何任务未能完成,您是正确的WhenAll 不会返回任何已完成 完成的结果,它只是包装了所有失败的聚合异常。幸运的是,您拥有原始任务集合,因此您可以从那里获得成功完成的结果。

var completedTasks = excutedTasks.Where(t => t.Status == TaskStatus.RanToCompletion);

只需使用它而不是 res

【讨论】:

    【解决方案2】:

    我试过你的代码,它工作得很好,除了取消的任务不是处于故障状态,而是处于取消状态。因此,如果您想处理取消的任务,请改用t.IsCanceled。未取消的任务运行完成。这是我使用的代码:

        public static async Task MainAsync()
        {
            var urls = new List<string> {"url1", "url2", "url3", "url4", "url5", "url6"};
    
            var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
    
            IEnumerable<Task<MyResults>> tasks =
                from url in urls
                select taskAsync(url).WithCancellation(cts.Token);
    
            Task<MyResults>[] excutedTasks = null;
    
            MyResults[] res = null;
            try
            {
                // Execute the query and start the searches:
                excutedTasks = tasks.ToArray();
    
                res = await Task.WhenAll(excutedTasks);
            }
            catch (Exception exc)
            {
                if (excutedTasks != null)
                {
                    foreach (Task<MyResults> faulted in excutedTasks.Where(t => t.IsFaulted))
                    {
                        // work with faulted and faulted.Exception
                    }
                }
            }
    
        }
    
        public static async Task<MyResults> taskAsync(string url)
        {
            Console.WriteLine("Start " + url);
            var random = new Random();
            var delay = random.Next(10);
            await Task.Delay(TimeSpan.FromSeconds(delay));
    
            Console.WriteLine("End " + url);
    
            return new MyResults();
        }
    
        private static void Main(string[] args)
        {
            MainAsync().Wait();
        }
    

    【讨论】:

    • 感谢您运行它!确实,任务会运行到完成,但是一旦发生取消,WhenAll 返回并且在res 中我没有得到任何结果。我期待在那里找到在超时之前完成的任务的结果。我添加了一些代码(在////////////// 之间显示这一点)。
    • (我编辑了我原来的问题以强调这个“结果”部分)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多