CSharp Task Asynchronous Programming

Last Updated: 2/7/2022

Exception Handling

  • Unhandled exceptions that are thrown by user code that is running inside a task are propagated back to the calling thread that is waiting for the task to finish or that access the Result property.
  • Unhandled exceptions should terminate the process. The calling code can handle them by enclosing the call in a try/catch statement.
  • Task Infrastructure wraps one or more exceptions in AggregateException. The AggregateException has InnerException property that can be enumerated to examine all the original exceptions that were thrown

Handling Exception

Exception From Single Task

var task = Task.Run(() => throw new Exception("An exception occured"));
try
{
	task.Wait();
}
catch(AggregateException ex)
{
	Console.WriteLine(ex.InnerExceptions[0].Message);
}

Using Exception Property

  • You can also retrieve the AggregateException exception from the task's Exception property.
  • You can check IsCompleted in a while loop for task completion instead of Wait method.
  • Use TaskStatus.Faulted to check if any exception occurred.
var task = Task.Run(() => throw new Exception("An exception occured"));

while(!task.IsCompleted) {}

if(task.Status == TaskStatus.Faulted) 
{
	Console.WriteLine(task.Exception.InnerExceptions[0].Message);
}

Exceptions from Multiple Task

Task[] taskArray = {
	Task.Run(() => throw new Exception("An exception occured from task 1")),
	Task.Run(() => throw new Exception("An exception occured from task 2")),
	Task.Run(() => throw new Exception("An exception occured from task 3"))
};
		
try
{
	Task.WaitAll(taskArray);
}
catch(AggregateException ex)
{
	foreach(var e in ex.InnerExceptions) {
		Console.WriteLine(e.Message);
	}
}

Exceptions from Attached Nested Task

var task = Task.Factory.StartNew(() => {
		var childTask1 = Task.Factory.StartNew(()=> {
			var childTask2 = Task.Factory.StartNew(() =>  throw new Exception("Exception from child2 task"), TaskCreationOptions.AttachedToParent);
			
			throw new Exception("Exception from child1 task");
		}, TaskCreationOptions.AttachedToParent);
	});
	
	try
	{
		task.Wait();
	}
	catch(AggregateException ex)
	{
		Console.WriteLine(ex.InnerExceptions[0].Message);
		//child1
		Console.WriteLine(((AggregateException)ex.InnerExceptions[0]).InnerExceptions[0].Message);
		}

Flattening Exceptions from Attached Nested Task

Use Flatten method to remove all nested aggregate exceptions and return original exceptions.

try
{
	task.Wait();
}
catch(AggregateException ex)
{
	foreach(var e in ex.Flatten().InnerExceptions) {
		Console.WriteLine(e.Message);
	}
}

Exception From Detached Child Task

Detached child task run independently of parent task To propagate the exception from child task access Wait or Result.

var task = Task.Factory.StartNew(() => {
	var childTask1 = Task<int>.Factory.StartNew(()=> {
		throw new Exception("Exception from child1 task");
	});
	
	//To propagate the exception access childTask1.Wait or childTask1.Result
	int result = childTask1.Result;
});

try
{
	task.Wait();
}
catch(AggregateException ex)
{
	foreach(var e in ex.Flatten().InnerExceptions) {
		Console.WriteLine(e.Message);
	}
}

Using handle method to filter exceptions

  • You can use the AggregateException.Handle method to filter out exceptions that you can treat as "handled" without using any further logic.
  • Any exceptions for which the delegate returns false are rethrown in a new AggregateException instance immediately after the AggregateException.Handle method returns.
private static void UsingAggregateExceptionHandle()
	{
		try
		{
			var files = GetAllFiles(@"C:\temp");
			foreach(var file in files)
				Console.WriteLine(file);
			
		}
		catch(AggregateException ae)
		{
			foreach(var ex in ae.InnerExceptions)
				Console.WriteLine("Tasks Exception: {0} {1}", ex.GetType().Name, ex.Message);
		}
		
		try
		{
			var files = GetAllFiles(null);
			foreach(var file in files)
				Console.WriteLine(file);
			
		}
		catch(AggregateException ae)
		{
			foreach(var ex in ae.InnerExceptions)
				Console.WriteLine("Tasks Exception: {0} {1}", ex.GetType().Name, ex.Message);
		}
	}
	
	private static string[] GetAllFiles(string path)
	{
		var task = Task.Run(() => Directory.GetFiles(path, "*.txt"));
		try
		{
			return task.Result;
		}
		catch(AggregateException ae)
		{
			ae.Handle(x => {
				if(x is DirectoryNotFoundException)
				{
					Console.WriteLine("directory not found exception handled");
				}
				return x is DirectoryNotFoundException;
			});
			return Array.Empty<String>();
		}
	}

Summary

  • One or more exceptions are wrapped in an AggregateException. The AggregateException has InnerException property that can be enumerated to examine all the original exceptions that were thrown
  • Handle the exception by enclosing the Wait or Result in try/catch block
  • You can also retrieve the exception from the task's Exception property.
  • Use Flatten method to remove all nested aggregate exceptions and return original exceptions.

Example

References

https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/exception-handling-task-parallel-library