Task.WhenAny 取消未完成的任务和超时 [英] Task.WhenAny with cancellation of the non completed tasks and timeout

查看:29
本文介绍了Task.WhenAny 取消未完成的任务和超时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的应用程序中,我正在创建一些并发 Web 请求,并且当其中任何一个请求完成时我感到满意,因此我使用了方法 Task.WhenAny:

var urls = new string[] {"https://stackoverflow.com","https://superuser.com","https://www.reddit.com/r/chess",};var 任务 = urls.Select(async url =>{使用 (var webClient = new WebClient()){return (Url: url, Data: await webClient.DownloadStringTaskAsync(url));}}).ToArray();var firstTask = await Task.WhenAny(tasks);Console.WriteLine($"第一个完成的网址:{firstTask.Result.Url}");Console.WriteLine($"Data: {firstTask.Result.Data.Length:#,0} 个字符");

<块引用>

第一个完成的网址:https://superuser.com
数据:121.954 个字符

我不喜欢这个实现的是,未完成的任务继续下载我不再需要的数据,浪费了我宁愿为下一批请求保留的带宽.所以我正在考虑取消其他任务,但我不知道该怎么做.我找到了如何使用 CancellationToken 取消特定的网络请求:

public static async Task<(string Url, string Data)>下载地址(字符串 url,CancellationToken 取消令牌){尝试{使用 (var webClient = new WebClient()){cancelToken.Register(webClient.CancelAsync);返回(网址,等待 webClient.DownloadStringTaskAsync(网址));}}catch (WebException ex) when (ex.Status == WebExceptionStatus.RequestCanceled){cancelToken.ThrowIfCancellationRequested();扔;}}

现在我需要一个 Task.WhenAny 的实现,它将采用一组 url,并将使用我的 DownloadUrl 函数来获取响应最快的站点的数据,并将处理较慢任务的取消逻辑.如果它有一个 timeout 参数,以提供对永无止境的任务的保护,那就太好了.所以我需要这样的东西:

public static Task>WhenAnyEx(这个 IEnumerable来源,Func任务工厂,整数超时){//在这里做什么?}

有什么想法吗?

解决方案

只需将相同的取消令牌传递给您的所有任务,如下所示:

CancellationTokenSource cts = new CancellationTokenSource();CancellationToken ct = cts.Token;//在这里你指定在取消之前要等待任务完成的时间int 超时 = 5000;cts.CancelAfter(超时);//将 ct 传递给您的所有任务并启动它们await Task.WhenAny(/* 你的任务在这里 */);//取消所有任务cts.Cancel();

此外,您需要阅读此线程以了解如何正确使用 CancellationToken:当我使用 CancelAfter() 时,任务仍在运行

In my app I am creating some concurrent web requests and I am satisfied when any one of them completes, so I am using the method Task.WhenAny:

var urls = new string[] {
    "https://stackoverflow.com",
    "https://superuser.com",
    "https://www.reddit.com/r/chess",
};
var tasks = urls.Select(async url =>
{
    using (var webClient = new WebClient())
    {
        return (Url: url, Data: await webClient.DownloadStringTaskAsync(url));
    }
}).ToArray();
var firstTask = await Task.WhenAny(tasks);
Console.WriteLine($"First Completed Url: {firstTask.Result.Url}");
Console.WriteLine($"Data: {firstTask.Result.Data.Length:#,0} chars");

First Completed Url: https://superuser.com
Data: 121.954 chars

What I don't like to this implementation is that the non-completed tasks continue downloading data I no longer need, and waste bandwidth I would prefer to preserve for my next batch of requests. So I am thinking about cancelling the other tasks, but I am not sure how to do it. I found how to use a CancellationToken to cancel a specific web request:

public static async Task<(string Url, string Data)> DownloadUrl(
    string url, CancellationToken cancellationToken)
{
    try
    {
        using (var webClient = new WebClient())
        {
            cancellationToken.Register(webClient.CancelAsync);
            return (url, await webClient.DownloadStringTaskAsync(url));
        }
    }
    catch (WebException ex) when (ex.Status == WebExceptionStatus.RequestCanceled)
    {
        cancellationToken.ThrowIfCancellationRequested();
        throw;
    }
}

Now I need an implementation of Task.WhenAny that will take an array of urls, and will use my DownloadUrl function to fetch the data of the fastest responding site, and will handle the cancellation logic of the slower tasks. It would be nice if it had a timeout argument, to offer protection against never-ending tasks. So I need something like this:

public static Task<Task<TResult>> WhenAnyEx<TSource, TResult>(
    this IEnumerable<TSource> source,
    Func<TSource, CancellationToken, Task<TResult>> taskFactory,
    int timeout)
{
    // What to do here?
}

Any ideas?

解决方案

Simply pass to all of your tasks the same cancellation token, something like this:

CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
// here you specify how long you want to wait for task to finish before cancelling
int timeout = 5000;
cts.CancelAfter(timeout);
// pass ct to all your tasks and start them
await Task.WhenAny(/* your tasks here */);
// cancel all tasks
cts.Cancel();

Also, you need to read this thread to be aware of how to use CancellationToken correctly: When I use CancelAfter(), the Task is still running

这篇关于Task.WhenAny 取消未完成的任务和超时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆