continuewith

Task.ContinueWith not very popular with async code?


We want to execute invoke 10 tasks in parallel and handle each of the 10 results in parallel.

to achieve, created a list of tasks and used continuewith each of which are associated to async methods,

snippet

private async Task<List<bool>> TransformJobsAsync(
            List<T> jobs)
        {
            var result = new List<bool>() { true };
            var tasks = new List<Task<bool>>(jobs.Count);

            try
            {
                foreach (var j in jobs)
                {
tasks .Add(InvokeSomeAsync(j).ContinueWith(x => HandleResultAsync(x, j)).Unwrap());
                }

                await Task.WhenAll(tasks);
                return tasks.Select(x => x.Result).ToList();
            }
            catch (Exception ex)
            {
                result = new List<bool>() { false };
            }
            return result;
        }



Task<(T response, T job)> InvokeSomeAsync        (T  job)
        {
            var cts = new CancellationTokenSource();

            try
            {
                cts.CancelAfter(30000);


var response = await SomeThirdPartyApi(request, cts.Token);

                if (response.HttpStatusCode == System.Net.HttpStatusCode.OK)
                {

                }


                return (response, job);
            }
            catch (OperationCanceledException opexException)
            {
                contextMessage = Messages.TranformationExecutionTimedOut;
            }
            catch (Exception ex)
            {
                contextMessage = Messages.UnHandledException;
            }
            finally
            {
                cts = null; //why? suggested pattern? review.
            }



            return await Task.FromException<(response, T Job)>(
                throw new Exception());
        }


    async Task<bool> HandleResultAsync(Task<(T response, T job)> task,                                                         T job)
            {

                try
                {
                    if (task.Status == TaskStatus.RanToCompletion)
                    {
                        if (task.Result.Status)
                        {
                            response = await CallMoreAsync(task.Result.reponse,
                                job, currentServiceState);
                        }
                        else
                        {
                            //log returned response = false
                        }
                    }
                    else
                    {
                        //log task failed
                    }
                }
                catch (Exception ex)
                {
                    response = false;
                }
                finally
                {
                    await DoCleanUpAsync();
                }
                return response;
            }

I wanted to know if there is any better pattern and continuewith is not appropriate to use!

Sometimes We get this error, System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error)


Solution

  • You should not use ContinueWith. ContinueWith is a low-level, dangerous way to do the same thing as await. Modern code should use await instead.

    To combine two asynchronous operations (e.g., InvokeSomeAsync and HandleResultAsync), introduce an async method:

    async Task<bool> InvokeAndHandleResultAsync<T>(T job)
    {
      var task = InvokeSomeAsync(job);
      return await HandleResultAsync(task, job);
    }
    

    This can then be used in your foreach:

    foreach (var j in jobs)
    {
      tasks.Add(InvokeAndHandleResultAsync(j));
    }
    

    Other notes:

    I'd write it something like:

    async Task InvokeAndHandleResultAsync<T>(T job)
    {
      using (var cts = new CancellationTokenSource(30000))
      {
        try
        {
          var response = await SomeThirdPartyApi(request, cts.Token);
          if (!response.Status)
          {
            //log returned response = false
            return;
          }
    
          await CallMoreAsync(response, job, currentServiceState);
        }
        catch (Exception ex)
        {
          //log task failed
        }
        finally
        {
          await DoCleanUpAsync();
        }
      }
    }
    

    Also, instead of building a list of tasks, you can simplify that code:

    private async Task TransformJobsAsync(List<T> jobs)
    {
      return Task.WhenAll(jobs.Select(j => InvokeAndHandleResult(j)));
    }