Home > other >  Why are only a random few of my Tasks from a Task list running to completion?
Why are only a random few of my Tasks from a Task list running to completion?

Time:09-30

Ooookay, so this has been a thorn in my side for a good week now.

First off, this is for Framework 4.8 (beyond my control).

I make a list of tasks, using Task.Run and a lambda to call an async function that calls a 3rd party API and returns a: Task<List<Dictionary<string, object>>>

Number of tasks is 75.

The API has pagination requirements, so the calling function takes start and end parameters, based on the iterator of the loop that builds the task list.

So far all straight forward.

Also straight forward is that after Task.WaitAll I get the wrong results, because closures close over variables, not over values. The iterator is at max long before the tasks have spun off, and thus the vast majority end up with the wrong pagination.

**** However, they do all run to completion.****

Now, whenever I try to solve the closure issue, I end up with a consistent result where only some of the tasks run to completion and the rest all fail. I have tried:

  • Assigning the iterator to a new variable in the lambda
  • Using Task.Factory.StartNew and passing the iterator in as a state object
  • Variations of the above, but with the start and end calculations moved to the calling function

In all cases the result remains the same: anywhere from a handful to a third of the tasks complete and the rest all fail, usually the former.

The AggregateException collection offers little to no insight as far as I can understand.

The general exception is the usual "One or more errors have occurred" and the inner exceptions hints at a movenext failure(?). Stack traces are minimal one-liners.

What could be going on here?

Thanks for the help.

-Willem

The calling function

public async Task<List<Dictionary<string, object>>> ExecuteAsyncJSON(
    string requestSource = "fields", string searchCount = "", Object iteration = null)
{
    string url = requestSource;

    // Add possible parameters.
    if (searchCount != "" && iteration != null)
    {

        // divide count by 500   remainder for pagination
        int quotient = (Int32.Parse(searchCount) / 500);
        int remainder = Int32.Parse(searchCount) % 500;

        int i = Convert.ToInt32(iteration);
        int add = i == quotient ? remainder : 500;
        int start = i * 500;
        int end = (i * 500)   add;

        if (url.Contains("?"))
        {
            url = url   "&start="   start.ToString()   "&end="   end.ToString();
        }
        else
        {
            url = url   "?start="   start.ToString()   "&end="   end.ToString();
        }
    }
    try
    {
        // make the call and get the response
        using (HttpResponseMessage response = await someclient.GetAsync(url))
            // this calls the API through an HttpClient
        {
            if (response.IsSuccessStatusCode)
            {
                var serializer = new JavaScriptSerializer();
                var responseBodyAsText = await response.Content.ReadAsStringAsync();
                var returnData = new List<Dictionary<string, object>>();
                returnData = serializer
                    .Deserialize<List<Dictionary<string, object>>>(responseBodyAsText);
                return returnData;
            }
            else
            {
                throw new Exception(response.ReasonPhrase);
            }
        }
    }
    catch
    {
        throw new Exception();
    }
}

The main stuff that tries to get this all done

var requestSource = "searches/"   searchId   "/members";

var searchCount = 37301;

int quotient = (Int32.Parse(searchCount) / 500);

var tasks = new List<Task<List<Dictionary<string, object>>>>();

// Put the task list together.
// Using the StartNew with state object to pass the iterator in.
for (var i = 0; i <= quotient; i  )
{
    var task = Task.Factory.StartNew((iteration) =>
    {
        var returnData = ExecuteAsyncJSON(requestSource, searchCount, iteration);
        return returnData;
    }, i)
    .Unwrap();

    tasks.Add(task);
}

try
{
    Task.WaitAll(tasks.ToArray());
}
catch (AggregateException ae)
{
    //throw ae;
}

List<Dictionary<string, object>> completedResults
    = new List<Dictionary<string, object>>();
List<Task<List<Dictionary<string, object>>>> faultedResults
    = new List<Task<List<Dictionary<string, object>>>>();

foreach (Task<List<Dictionary<string, object>>> listTask in tasks)
{
    if (listTask.Status == TaskStatus.RanToCompletion)
        completedResults.AddRange(listTask.Result);

    if (listTask.Status == TaskStatus.Faulted)
        faultedResults.Add(listTask);
}

Debug Data

The WaitAll AggregateException is:

One or more errors occurred

Exception of type 'System.Exception' was thrown.
    at System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout, CancellationToken cancellationToken)
    at System.Threading.Tasks.Task.WaitAll(Task[] tasks, Int32 millisecondsTimeout)
    at System.Threading.Tasks.Task.WaitAll(Task[] tasks)
    at Smu.Bbec.Emma.API.EmmaAPI.<GetMembersMatchingSearch>d__22.MoveNext() in [reference to Task.WaitAll(tasks.ToArray());]

Out of 76 calls, 4 completed just fine and then 72 fail with the exact same inner exception:

System.Exception: Exception of type 'System.Exception' was thrown.
    at Smu.Bbec.Emma.API.EmmaAPI.<ExecuteAsyncJSON>d__8.MoveNext() in [reference to code that makes the actual call to the API]

I set some breakpoints and this is the result:

A task was canceled.
    at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
    at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
    at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
    at Smu.Bbec.Emma.API.EmmaAPI.<ExecuteAsyncJSON>d__8.MoveNext() in f:\\TestApps\\Smu.Bbec.Emma\\Smu.Bbec.Emma.API\\EmmaAPI.cs:line 100"

No inner exception.

And this variety;

An error occurred while sending the request.
Inner exception: The request was aborted: The request was canceled (System.Exception {System.Net.WebException})

This is the full debug data I have on the cancelation (sensitive data removed):

$exception  {"A task was canceled."}    System.Threading.Tasks.TaskCanceledException
this    {**********.EmmaAPI}    **********.EmmaAPI
    _accountId  "********"  string
    _authorization  "MTg4NzblahblahblahblahmM6MDNmYzU1blahblahblahblahjM="  string
    _baseUri    {https://api.e2ma.net/********/}    System.Uri
    _baseUrl    "https://api.e2ma.net/********/"    string
    _publicKey  "********"  string
    _secretKey  "********"  string
    emmaClient  {System.Net.Http.HttpClient}    System.Net.Http.HttpClient
    BaseAddress {https://api.e2ma.net/********/}    System.Uri
DefaultRequestHeaders   {Accept: application/json Authorization: Basic MblahblahblahblahTFjYmblahblahblahblahM4M2IyMjM=} System.Net.Http.Headers.HttpRequestHeaders
MaxResponseContentBufferSize    2147483647  long
Timeout {00:01:40}  System.TimeSpan
baseAddress {https://api.e2ma.net/********/}    System.Uri
defaultRequestHeaders   {Accept: application/json Authorization: Basic MTgblahblahblahblah4Y2blahblahblahblahNmYzblahblahblahblahjM=} System.Net.Http.Headers.HttpRequestHeaders
disposeHandler  true    bool
disposed (System.Net.Http.HttpMessageInvoker)   false   bool
disposed    false   bool
handler {System.Net.Http.HttpClientHandler} System.Net.Http.HttpMessageHandler {System.Net.Http.HttpClientHandler}
    maxResponseContentBufferSize    2147483647  long
    operationStarted    true    bool
    pendingRequestsCts  {System.Threading.CancellationTokenSource}  System.Threading.CancellationTokenSource
timeout {00:01:40}  System.TimeSpan
Static members      
requestSource   "searches/17297143/members" string
searchCount "37810" string
iteration   67  object {int}
url "searches/17297143/members?start=33500&end=34000"   string

CodePudding user response:

I found the answer to my conundrum.

It turned out to be the same issue as posted here:

c# a task was canceled at the await call

The solution for me was to add .Timeout = TimeSpan.FromMinutes(30) to my HttpClient.

Thanks to Theodor Zoulias, Stephen Cleary, and everyone else who helped me out here.

Cheers!

CodePudding user response:

Replace the loop with LINQ extension methods, toss the superfluous task/unwrap and see if that helps you.

var requestSource = ...;
var searchCount = ...;
var quotient = ...;

var tasks = Enumerable.Range(0, quotient)
    .Select(i => ExecuteAsyncJSON(requestSource, searchCount, i))
    .ToArray();

Task.WaitAll(tasks);
  • Related