简体   繁体   English

如何 - 具有超时和取消的多个异步任务

[英]How to - Multiple Async tasks with timeout and cancellation

I would like to fire several tasks while setting a timeout on them. 我想在设置超时时触发几项任务。 The idea is to gather the results from the tasks that beat the clock, and cancel (or even just ignore) the other tasks. 我们的想法是从击败时钟的任务中收集结果,并取消(甚至忽略)其他任务。

I tried using extension methods WithCancellation as explained here , however throwing an exception caused WhenAll to return and supply no results. 我尝试使用扩展方法WithCancellation作为解释这里 ,但是抛出一个异常造成WhenAll返回和供应没有结果。

Here's what I tried, but I'm opened to other directions as well (note however that I need to use await rather than Task.Run since I need the httpContext in the Tasks): 这是我尝试过的,但我也向其他方向开放(注意我需要使用await而不是Task.Run,​​因为我需要在Tasks中使用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

EDIT: Following @Servy's answer below, this is the implementation I went with: 编辑:关注 @ 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));

If any of the tasks fail to complete you are correct that WhenAll doesn't return the results of any that did complete, it just wraps an aggregate exception of all of the failures. 如果有任何的任务无法完成,你是正确的, WhenAll不返回任何没有完成的结果,它只是包装的所有故障的集合例外。 Fortunately, you have the original collection of tasks, so you can get the results that completed successfully from there. 幸运的是,您拥有原始的任务集合,因此您可以从那里获得成功完成的结果。

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

Just use that instead of res . 只需使用它而不是res

I tried you code and it worked just fine, except the cancelled tasks are in not in a Faulted state, but rather in the Cancelled. 我尝试了你的代码并且它运行得很好,除了取消的任务不处于Faulted状态,而是处于Cancelled状态。 So if you want to process the cancelled tasks use t.IsCanceled instead. 因此,如果您想要处理已取消的任务,请使用t.IsCanceled The non cancelled tasks ran to completion. 未取消的任务已完成。 Here is the code I used: 这是我使用的代码:

    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();
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM