如何使用CancellationToken取消任务并等待Task.WhenAny

时间:2018-03-31 13:21:53

标签: c# multithreading asynchronous async-await cancellationtokensource

我有Web应用程序,它调用不同的Web服务来执行不同的工作。一些Web服务需要很长时间,有些很短。应用程序将并行调用所有Web服务以在完成或异常时获得结果。我已经开始编写代码,但我需要知道如何以最佳方式实现以下工作。

  1. 如果任何Web服务需要超过5秒,任务将被取消,但剩余的任务将继续调用Web服务以获得结果或其他任务。
  2. 我在示例代码中使用了CancellationTokenSource但是 OperationCanceledException没有捕获。它始终是异常捕获。
  3. 最佳做法是限制执行我的网络服务的线程数量?如果是这样,我如何限制线程并重用它们。
  4. 任何示例代码或网址都会对我有帮助。

    protected async void btnAAC_Click(object sender, EventArgs e)
    {
        CancellationTokenSource cts = new CancellationTokenSource();
        cts.CancelAfter(5000); 
        string[] retResult = null;
    
        Task<string[]> taskResult = Run(cts.Token); 
    
        try
        {
            retResult = await taskResult;
            //To do : display data
        }
        catch (OperationCanceledException ocex)
        {
            resultsTextBox.Text += "\r\nDownloads canceled.\r\n";
        }
        catch(Exception ex)
        {
            Debug.WriteLine(ex.ToString());
        }
        finally
        {
            cts.Dispose();
        }  
    }
    
    private async static Task<string[]> Run(CancellationToken cancellationToken)
    {
        HashSet<Task<string>> tasks = new HashSet<Task<string>>();
        List<string> urlList = ServiceManager.SetUpURLList();
        List<string> finalResult = new List<string>();
    
        foreach (var work in urlList)
        {
            tasks.Add(Task.Run(() => DoWork(work, cancellationToken)));
        }
    
        try
        {
            while (tasks.Count > 0)
            {
                Task<string> finishedTask = await 
                        Task.WhenAny(tasks.ToArray());
                tasks.Remove(finishedTask);
                finalResult.Add(finishedTask.Result);
            }
    
            return finalResult.ToArray();
        }
        finally
        {
            //CleanUpAfterWork();
        }
    }
    
    public async static Task<string> DoWork(string url, CancellationToken cancellationToken)
    {
        while (true)
        {            
            cancellationToken.ThrowIfCancellationRequested();
    
            var html = await Downloader.DownloadHtml(url);
    
            return html;
        }
    }
    

1 个答案:

答案 0 :(得分:1)

  

如果任何网络服务需要超过5秒,任务将被取消,但剩余的任务将继续调用网络服务以获得结果或其他任务。

以下代码示例包含一种方法(and a Fiddle)。其RunTimeLimitedTask方法包含策略的关键部分。

public static void Main()
{
    DoIt().GetAwaiter().GetResult();
}

public async static Task DoIt() 
{
    var results = await Task.WhenAll(
        RunTimeLimitedTask(10),
        RunTimeLimitedTask(1000));

    foreach(var result in results) Console.WriteLine(result);
}

public async static Task<string> RunTimeLimitedTask(int timeLimit)
{
    // A
    var source = new CancellationTokenSource();
    source.CancelAfter(timeLimit);

    // B
    try {
        await Task.Run(async () => await Task.Delay(500, source.Token));

         // C
        return "Complete";
    }
    catch (TaskCanceledException) {
        // C
        return "Cancelled";
    }
}

答:为每个网络请求创建一个新的有时间限制的CancellationSource

B:在处理TaskCanceledException的try catch块中包装等待的Web请求(我们模仿延迟的Task)。

C:通过返回传达成功/失败的字符串来处理成功和取消。要更好地制定策略,请返回具有更多结构的内容,例如Result<TSuccess, TFailure>而不是返回string