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

3
我希望在设置超时时间的同时启动多个任务。我的想法是收集在规定时间内完成的任务结果,并取消(或者忽略)其他任务。
我尝试使用这里所解释的WithCancellation扩展方法,但是抛出异常导致WhenAll返回并且没有提供任何结果。
以下是我尝试过的内容,但我也愿意接受其他方向的建议(请注意,由于需要在任务中使用httpContext,因此我需要使用await而不是Task.Run):
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 是做什么用的? - NeddySpaghetti
在我的情况下,它们使用httpClient检索信息,但我认为任何异步任务都可以(您下面提供的那个应该很好)。关键是taskAsync不接收取消标记。 - Erez Cohen
2个回答

1
如果有任何一个任务未能完成,你是正确的,WhenAll 不会返回任何已经完成的结果,它只是将所有失败的异常聚合成一个。幸运的是,你有原始的任务集合,所以你可以从那里获取成功完成的结果。
var completedTasks = excutedTasks.Where(t => t.Status == TaskStatus.RanToCompletion);

直接使用它,而不是res


0

我尝试了你的代码,它很好用,只是被取消的任务并非处于错误状态,而是处于“已取消”状态。因此,如果你想要处理被取消的任务,请使用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 中我得不到任何结果。我原本期望在那里找到超时之前已经完成的任务的结果。我添加了一些代码(在 ////////////// 之间)来展示这一点。 - Erez Cohen
Results:结果: - Erez Cohen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接