首页 > 解决方案 > 使用取消标记的 C# 异步/并行编程

问题描述

IService我有一个实现接口的服务列表。我还有一个实现ICalculator接口的计算服务。

服务

public interface IService
{
    Task<List<Response>> GetSomeDataAsync(CancellationToken cancellationToken = default);
}

计算器

public interface ICalculator
{
    Task<List<Response>> CalculateAsync(Request request, CancellationToken cancellationToken = default);
}

我希望所有实现的服务并行IService运行,并在每个服务的结果上运行。IService.GetSomeDataAsync()ICalculator.CalculateAsync()

public async Task<List<Response>> Run()
{
    var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10));

    var data = await Task.WhenAll(_services.Select(service =>
    {
        var results = service.GetSomeDataAsync(tokenSource.Token);

        var newResults = results.ContinueWith(x =>
        {
            var request = new request
            {
                Data = x.Result
            };

            return _calculator.CalculateAsync(request, tokenSource.Token);
        }, tokenSource.Token);

        return newResults;
    }));

    var response = data.SelectMany(o => o.Result)
        .ToList();

    return response;
}

这一切都按预期工作。调用x.Resultando.Result不会锁定线程,因为任务在调用时已完成。当我尝试实现取消令牌时,问题就出现了。我希望能够Iservice.GetSomeDataAsync()在需要很长时间时取消。我也不言而喻,如果IService.GetSomeDataAsync()被取消,那么ICalculator.CalculateAsync()也不应该运行。

我设置CancellationTokenSource为在 10 秒内取消。这可行,但它抛出了我认为的错误,OperationCanceledException但我无法继续执行该程序或捕获错误。

IService.GetSomeDataAsync()方法看起来像这样。

public async Task<List<Response>> GetSomeDataAsync(CancellationToken cancellationToken)
{
    try
    {
        var result = await "http://slowwly.robertomurray.co.uk/delay/100000/url/http://www.google.co.uk"
            .GetStringAsync(cancellationToken);
    }
    catch (OperationCanceledException x)
    {
        Console.WriteLine(x);
    }

    return result;
}

*请注意,我使用 Flurl 进行 Web 请求。我将 传递tokenSource.Token给通过包含方法参数传入的方法。

ICalculator.CalculateAsync()方法看起来像这样。

public async Task<List<Response>> CalculateAsync(Request request, CancellationToken cancellationToken = default)
{
    try
    {
        var result = await "http://slowwly.robertomurray.co.uk/delay/2000/url/http://www.google.co.uk"
            .GetStringAsync(cancellationToken);
    }
    catch (OperationCanceledException x)
    {
        Console.WriteLine(x);
    }

    return result;
}

*请注意,我使用 Flurl 进行 Web 请求。我将 传递tokenSource.Token给通过包含方法参数传入的方法。

所以问题是我如何正确使用取消令牌来达到预期的结果?

标签: c#.net-corecancellationtokensourceflurl

解决方案


这个解决方案对我有用。

public async Task<List<Response>> Run()
{
    var data = await Task.WhenAll(_services.Select(async service =>
    {
        var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(10));

        try
        {
            results = await service.GetSomeDataAsync(tokenSource.Token);
        }
        catch (Exception x)
        {
            return new List<Response>();
        }

        var request = new Request
        {
            Data = results
        };

        try
        {
            return await _calculator.CalculateAsync(request, tokenSource.Token);
        }
        catch (Exception x)
        {
            return new List<Response>();
        }
    }));

    var response = data
        .SelectMany(responses => responses)
        .ToList();

    return response;
}

推荐阅读