2

My question is about capturing exceptions in ForAll method under Plinq

I was trying to run tasks concurently with setting max number of threads. Using enumerable .AsParallel() .WithDegreeOfParallelism(100) .ForAll(async item => await AsyncTask())

It works, but if AsyncTask throws exception the app crashes.

I have done the following test:

try
{
   IEnumerable<string> enumerable = new List<string> { "st", "st" };
   enumerable.AsParallel()
   .ForAll(async f =>
   {
      try
      {
          throw new Exception(); // Or await AsyncTask that throws this
      }
      catch (Exception e)
      {
          e.ToString(); **// This Exception is captured**
          throw e;
      }
   });
}
catch (Exception e) **// THIS IS NOT CAPTURED AND THE APP CRASHES**
{
   e.ToString();
}
  1. And I would like to understand the reasons for this
  2. And other options to implement
Pankwood
  • 1,799
  • 5
  • 24
  • 43
  • Have a look at this answer : https://stackoverflow.com/questions/5383310/catch-an-exception-thrown-by-an-async-method – Sam Jul 30 '18 at 17:40

1 Answers1

2

enumerable.AsParallel().ForAll() executes the given action for each element of your enumeration in parallel. Since your given action is async by itself, ForAll() does not wait until all actions completed. In this case the executed code leaves the try..catch block before your AsyncTask() method throws the exception. This may lead to an unhandled exception, which crashes your app.

It does not matter, that you try to await the AsyncTask(), because ForAll() gets a plain Action and does not await the result of your AsyncTask().

A possible solution could be to start your AsyncTasks for each element without AsParallel().ForEach() and await the results later inside your try..catch.

When storing the

Task or Task<T>

result in a list you can check if any task was throwing an exception using the task.Exception property.

You can do something like this:

    private async Task DoSomethingAsync()
    {
        try
        {
            IEnumerable<string> enumerable = new List<string> { "st", "st" };

            // start all tasks and store them in an array
            var tasks = enumerable.Select(TaskAsync).ToArray();

            // do something more without waiting until all tasks above completed
            // ...


            // await all tasks
            var completionTask = Task.WhenAll(tasks);
            await completionTask;

            // handle task exception if any exists
            if (completionTask.Status == TaskStatus.Faulted)
            {
                foreach (var task in tasks)
                {
                    if (task.Exception != null)
                    {
                        // throw an exception or handle the exception, e.g. log the exceptions to file / database
                    }
                }
            }
        }
        catch (Exception e)
        {
            // handle your exception, e.g. write a log to file / database
        }
    }

    private Task TaskAsync(string item)
    {
        // Task.Delay() is just a placeholder
        // do some async stuff here, e.g. access web services or a database
        return Task.Delay(10000);
    }
Steffen R.
  • 96
  • 5