Reputation: 6765
I have a list of services that implement the IService
interface. I also have a calculation service that implements the ICalculator
interface.
IService
public interface IService
{
Task<List<Response>> GetSomeDataAsync(CancellationToken cancellationToken = default);
}
ICalculator
public interface ICalculator
{
Task<List<Response>> CalculateAsync(Request request, CancellationToken cancellationToken = default);
}
I want all the services that implement IService
to run IService.GetSomeDataAsync()
in parallel and the ICalculator.CalculateAsync()
to run on the results of each.
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;
}
This all works as expected. The calls to x.Result
and o.Result
will not lock up the thread because the tasks are completed by the time its called. The issue comes when I try to implement a cancellation token. I want to be able to cancel Iservice.GetSomeDataAsync()
when it is taking to long. I also goes with out saying that if IService.GetSomeDataAsync()
gets cancelled then ICalculator.CalculateAsync()
should not run either.
I set the CancellationTokenSource
to cancel in 10 seconds. This works but it throws what I believe to be the OperationCanceledException
but I am not able to move on with the program or catch the error.
The IService.GetSomeDataAsync()
method looks something like this.
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;
}
*Note that I am using Flurl for the web request. I pass the
tokenSource.Token
to the method that was passed in via the containing method parameters.
The ICalculator.CalculateAsync()
method looks something like this.
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;
}
*Note that I am using Flurl for the web request. I pass the
tokenSource.Token
to the method that was passed in via the containing method parameters.
So the question is how do I correctly work with the cancellation token to achieve the intended results?
Upvotes: 0
Views: 460
Reputation: 6765
This solution worked for me.
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;
}
Upvotes: 1