Search code examples
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:

    • CancellationTokenSource should be disposed.
    • await Task.From* is usually a yellow flag.
    • Using Task.Status is a red flag.
    • Use exceptions for exceptional situations, rather than having a bool result with a contextMessage state somewhere else.

    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)));
    }