CSharp Task Asynchronous Programming

Last Updated: 2/7/2022

Task Cancellation

  • Task and Task<TResult> support cancellation through the use of cancellation token.
  • In the Task classes, cancellation involves cooperation between the user delegate, which represents a cancelable operation, and the code that requested the cancellation.
  • A successful cancellation involves the requesting code calling the Cancel method on CancellationTokenSource object and the user delegate terminating the operation in a timely manner by returning from the delegate or throwing ThrowIfCancellationRequested

On Cancel throw exception

  • Create an object for CancellationTokenSource
  • Get the token object from Token property of token source
  • Pass the token object to the Task.Run method
  • Call Cancel method on token source object to raise a cancel request.
  • In the task delegate check for IsCancellationRequested and terminate the operation

Task status is set to Cancelled state when ThrowIfCancellationRequested method is called.

var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

var task = Task.Run(() => {
	while(true) {
		token.ThrowIfCancellationRequested();
		if(token.IsCancellationRequested)
		{
			// add cleanup code	
			token.ThrowIfCancellationRequested();
		}
	}
}, token);

Thread.Sleep(2000);
tokenSource.Cancel();

try 
{
	task.Wait();
}
catch(AggregateException e) 
{
	Console.WriteLine("Task Status: {0}, Error Messaage: {1}", task.Status, e.InnerException.Message);		
}
finally 
{
	tokenSource.Dispose();
}

On Cancel return

Task status is set to RanToCompletion when the code simply returns.

var tasks = new List<Task<int>>();
	var tokenSource = new CancellationTokenSource();
	var token = tokenSource.Token;
	int completedIterations = 0;
	
	for(int i=0; i<10; i++) {
		tasks.Add(Task.Run(() => {
			int x=0;
			for(x =0; x<200000; x++)
			{
				if(token.IsCancellationRequested)
					return x;
			}
			Interlocked.Increment(ref completedIterations);
			
			if(completedIterations > 5 && !tokenSource.IsCancellationRequested) {
				tokenSource.Cancel();
			}
			return x;
		}, token));
	}
	
	try
	{
		Task.WaitAll(tasks.ToArray());
	}
	catch(AggregateException)
	{
		Console.WriteLine("{0, 10} {1,20} {2, 14}", "Task Id", "Status", "Iterations");
		foreach(var t in tasks)
		{
			Console.WriteLine("{0, 10} {1,20} {2, 14:N0}", t.Id, t.Status, t.Status != TaskStatus.Canceled ? t.Result.ToString(): "Not Started");
		}
	}
}

Cancel a Task and its Children

private static async Task CancelTaskAndChildren() 
{
	var tasks = new ConcurrentBag<Task>();
	var tokenSource = new CancellationTokenSource();
	var token = tokenSource.Token;
	Task t1 = Task.Run(() => DoSomeWork(1, token), token);
	Console.WriteLine($"Task {t1.Id} executing");
	tasks.Add(t1);
	
	Task t2 = Task.Run(() => {
		Task t;
		for(int i=3; i<=10; i++) {
			t = Task.Factory.StartNew((obj) => DoSomeWork((int)obj, token), i, token);
			Console.WriteLine($"Task {t.Id} executing");
			tasks.Add(t);
		}
		DoSomeWork(2, token);
	}, token);
	Console.WriteLine($"Task {t2.Id} executing");
	tasks.Add(t2);
	
	Task.Delay(100).Wait();
	tokenSource.Cancel();
	Console.WriteLine($"Tasks cancellation requested");
	
	try
	{
		await Task.WhenAll(tasks.ToArray());
	}
	catch(OperationCanceledException e) 
	{
		Console.WriteLine(nameof(OperationCanceledException));
	}
	finally 
	{
		tokenSource.Dispose();
	}
	
	foreach(var task in tasks)
		Console.WriteLine($"Task {task.Id} {task.Status}");
}

private static void DoSomeWork(int taskNum, CancellationToken ct) 
{
	if(ct.IsCancellationRequested) 
	{
		Console.WriteLine($"Task {taskNum} was cancelled before it got started");
		ct.ThrowIfCancellationRequested();
	}
	
	var sw = new SpinWait();
	for(int i=0; i<100; i++)
	{
		sw.SpinOnce();
		if(ct.IsCancellationRequested) 
		{
			Console.WriteLine($"Task {taskNum} cancelled");
			ct.ThrowIfCancellationRequested();
		}
	}
	Console.WriteLine($"Task {taskNum} completed");
}

Example

References