Search code examples
c#asynchronoustaskcontinuations

Task.ContinueWith() is not executing as expected


Im trying to rewrite some of my old code using async/await and chaining Tasks using ContinueWith() and checking for exceptions with TaskContinuationOptions.NotOnFaulted.

When I debug the code I noticed that it does not run as I expected. Both webrequests are successful but only the first continuation processes the response.

The second continuation does not complete and the last one gives me the result:

Id = 1, Status = RanToCompletion, Method = "{null}", Result = "System.Threading.Tasks.Task`1[System.Threading.Tasks.VoidTaskResult]"

and the result:

Id = 2, Status = WaitingForActivation, Method = "{null}", Result = "{Not yet computed}"

My question is what am I doing wrong and what can I do so the second continuation completes. I am also interested if it is considered good practice to chain tasks together using ContinueWith or if there is a better way to do it without writing a bunch of clumsy methods? Thanks for the help

   using Newtonsoft.Json.Linq;                  

   var api = new Api();
   var order = new Dictionary<string, object>();
   await api.MakeRequest(Api.Endpoint.Orders, HttpMethod.Get, null, "?completed=false&page=" + count)
   //Look for new Orders
   .ContinueWith(ant =>
    {
          dynamic jsonOrder = JObject.Parse(ant.Result);
          JArray data = jsonOrder.data;
          //Process Json Response
          order.Add("customer_name", (string)data[j]["customer_name"]);
          order.Add("product_id", (string)data[j]["product_id"]);
          order.Add("order_id", (string)data[j]["order_id"]);
          order.Add("timestamp", (int)data[j]["timestamp"]);
          //Entries are successfully added
    }, TaskContinuationOptions.NotOnFaulted )
    //Now get more details about the product
    .ContinueWith(async (ant) =>
    {
          string result = await api.MakeRequest(Api.Endpoint.Product, HttpMethod.Get, null, (string)order["product_id"]); 
          //The Request succeeds 

          //This code block does not execute
          dynamic json = JObject.Parse(result);
          order.Add("deadline", (int)json.data.deadline);
          order.Add("price", (string)json.data.price);
          order.Add("amount", (int)json.data.amount);
          //This code block does not execute 

     }, TaskContinuationOptions.NotOnFaulted)
     //Get some more details about the Customer (isRecurring? etc)
     .ContinueWith(async (ant) =>
     {
        //Some more code here
     }

Solution

  • Like @Ben Robinson said the use of await automatically registers the rest of the method as a continuation which is only executed if the operation succeeds, otherwise an exception is thrown. I'd change my method to remove the ContinueWith calls and consider using ConfigureAwait(false) if you don't need to return to the current SynchrionizationContext after the asynchronous operations are finished, i.e. the rest of the method will continue execution on the thread pool thread. You may also find this article useful.

    var api = new Api();
    var order = new Dictionary<string, object>();
    
    await api.MakeRequest(Api.Endpoint.Orders, HttpMethod.Get, null, "?completed=false&page=" + count).ConfiugureAwait(false);
    
    //Look for new Orders
    dynamic jsonOrder = JObject.Parse(ant.Result);
    JArray data = jsonOrder.data;
    //Process Json Response
    order.Add("customer_name", (string)data[j]["customer_name"]);
    order.Add("product_id", (string)data[j]["product_id"]);
    order.Add("order_id", (string)data[j]["order_id"]);
    order.Add("timestamp", (int)data[j]["timestamp"]);
    
    //Now get more details about the product
    string result = await api.MakeRequest(Api.Endpoint.Product, HttpMethod.Get, null, (string)order["product_id"]).ConfiugureAwait(false);
    
    dynamic json = JObject.Parse(result);
    order.Add("deadline", (int)json.data.deadline);
    order.Add("price", (string)json.data.price);
    order.Add("amount", (int)json.data.amount);