Home > Software design >  Why the Task gets Cancelled
Why the Task gets Cancelled

Time:02-05

I'd like to spawn some threads and in each thread sequentially make calls to an API and aggregate the results (some sort of stress testing). Here is my attempt:

private async Task DoWork()
{
    var allResponses = new List<int>();
    for (int i = 0; i < 10; i  )
    {
        await Task.Run(() =>
        {
            var responses = Enumerable.Range(0, 50).Select(i => CallApiAndGetStatusCode());
            allResponses.AddRange(responses);
        });
    }

    // do more work
}


private int CallApiAndGetStatusCode()
{
    try
    {
        var request = new HttpRequestMessage(httpMethod.Get, "some url");

        var responseResult = httpClient.SendAsync(request).Result;
        return (int)responseResult.StatusCode;
    }
    catch (Exception e)
    {
        logger.LogError(e, "Calling API failed");
    }
}

However, this code always ends up the catch with the inner exception being {"A task was canceled."}. What am I doing wrong here?

CodePudding user response:

There is no benefit to using either Enumerable.Range or .AddRange in your example, since you do not need the seeded number. Your code must be converted to async/await to avoid deadlocks and in doing so, you can simply loop inside of each task and avoid any odd interactions between Enumerable.Select and await:

private async Task DoWork()
{
    var allTasks = new List<Task>(10);
    var allResponses = new List<int>();
    
    for (int i = 0; i < 10; i  )
    {
        allTasks.Add(Task.Run(async () =>
        {
            var tempResults = new List<int>();
            
            for (int i = 0; i < 50; i  )
            {
                var result = await CallApiAndGetStatusCode();
                if (result > 0) tempResults.Add(result);
            }
            
            if (tempResults.Count > 0)
            {
                lock (allResponses)
                {
                    allResponses.AddRange(tempResults);
                }
            }
        }));        
    }

    await Task.WhenAll(allTasks);

    // do more work
}


private async Task<int> CallApiAndGetStatusCode()
{
    try
    {
        var request = new HttpRequestMessage(HttpMethod.Get, "some url");

        var responseResult = await httpClient.SendAsync(request);
        return (int)responseResult.StatusCode;
    }
    catch (Exception e)
    {
        logger.LogError(e, "Calling API failed");
    }
    
    return -1;
}

Note that this code is overly protective, locking the overall batch before adding the temp results.

CodePudding user response:

I changed your code to this and work

async Task DoWork()
{
    var allResponses = new List<int>();
    for (int i = 0; i < 10; i  )
    {
        await Task.Run(() =>
        {
            var responses = Enumerable.Range(0, 3).Select(i => CallApiAndGetStatusCodeAsync());
            allResponses.AddRange(responses.Select(x => x.Result));
        });
    }

    // do more work
}

async Task<int> CallApiAndGetStatusCodeAsync()
{
    try
    {
        var request = new HttpRequestMessage(HttpMethod.Get, "http://www.google.com");
        var responseResult = await httpClient.SendAsync(request);
        return (int)responseResult.StatusCode;
    }
    catch (Exception e)
    {
        logger.LogError(e, "Calling API failed");
        return -1;
    }
}
  • Related