CSharp Task Asynchronous Programming

Last Updated: 2/3/2022

Waiting For Tasks

Wait Methods

  • Task class provide several overloads of Wait methods that enable you to wait for a task to finish.
  • Static WaitAll method let you wait for all of an array of tasks to finish.
  • Static WaitAny method let you wait for any of an array of tasks to finish.

Reasons For Waiting

Typically, you would wait for a task for one of these reasons:

  • The main thread depends on the final result computed by a task.
  • You have to handle exceptions that might be thrown from the task.
  • The application may terminate before all tasks have completed execution. For example, console applications will terminate as soon as all synchronous code in Main (the application entry point) has executed.

Wait

Waits for the Task to complete execution.

Wait with No Args

Task taskA = new Task( () => Console.WriteLine("Hello from taskA."));
taskA.Start();
taskA.Wait();

Wait with Timeout

Task taskA = Task.Run(() => DoComputation());
if(!taskA.Wait(100))
	Console.WriteLine("The timeout interval elapsed");
Task taskA = Task.Run(() => DoComputation());
if(!taskA.Wait(TimeSpan.FromMilliseconds(50)))
	Console.WriteLine("The timeout interval elapsed");

WaitAll

Waits for all of the provided Task objects to complete execution.

Code Syntax

Task[] tasks = new Task[3]
{
    Task.Run(() => MethodA()),
    Task.Run(() => MethodB()),
    Task.Run(() => MethodC())
};

//Block until all tasks complete.
Task.WaitAll(tasks);

Code Example

private static Action<Object> PrintThreadInfo = (object input) => {
	int i = (int)input;
	if(i >= 2 && i <= 5)
	{
		throw new InvalidOperationException("Simulated Exception");
	}
	Console.WriteLine($"Task Id: {Task.CurrentId}, Input: {i}, TickCount: {Environment.TickCount}, ThreadId: {Thread.CurrentThread.ManagedThreadId}");
};
		
	
private static void WaitAllForTasks()
{
	var tasks = new List<Task>();
	for(int i = 0; i < 10; i++)
	{
		tasks.Add(Task.Factory.StartNew(PrintThreadInfo, i));
	}
	try 
	{
		Task.WaitAll(tasks.ToArray());
	}
	catch(AggregateException e)
	{
		for(int j = 0; j < e.InnerExceptions.Count; j++)
		{
			Console.WriteLine(e.InnerExceptions[j].Message);
		}
	}
}

WaitAny

  • Waits for any of the provided Task objects to complete execution.
  • Returns the index of the completed task object in the tasks array.

Code Syntax

Task[] tasks = new Task[3]
{
    Task.Run(() => MethodA()),
    Task.Run(() => MethodB()),
    Task.Run(() => MethodC())
};

//Block until any of tasks complete.
Task.WaitAny(tasks);

Code Example

private static void WaitForAnyTask()
{
	Task[] tasks = new Task[5];
	for (int i = 0; i <= 4; i++) {
		int factor = i;
		tasks[i] = Task.Run(() => Thread.Sleep(factor * 250 + 50));
	}
	int index = Task.WaitAny(tasks);
	Console.WriteLine("Wait ended because task #{0} completed.", tasks[index].Id);
	Console.WriteLine("Current Status of Tasks:");
	foreach (var t in tasks)
		Console.WriteLine("Task {0}: {1}", t.Id, t.Status);
}

WhenAll

Creates a task that will complete when all of the supplied tasks have completed. Preferred over WaitAll

Code Syntax

Task[] tasks = new Task[3]
{
    Task.Run(() => MethodA()),
    Task.Run(() => MethodB()),
    Task.Run(() => MethodC())
};

Task t = Task.WhenAll(tasks);
t.Wait();

Code Example

private static int failed = 0;
private static void WhenAllForTasks()
{
	List<Task> tasks = PingWebsites();
	Task t = Task.WhenAll(tasks);
	try
	{
		t.Wait();
	}
	catch {}
	
	if(t.Status == TaskStatus.RanToCompletion)
		Console.WriteLine("All tasks completed");
	else
		Console.WriteLine("{0} tasks failed", failed);
}

private static List<Task> PingWebsites() 
{
	failed = 0;
	string[] urls = {
		"www.websitenotfound1.com", 
		"www.websitenotfound2.com", 
		"www.google.com"
	};
	var tasks  = new List<Task>();
	
	foreach(var url in urls) 
	{
		tasks.Add(Task.Run(() => {
			Console.WriteLine($"Url {url}, Thread Id: {Thread.CurrentThread.ManagedThreadId}");
			var ping = new Ping();
			try
			{
				var reply = ping.Send(url);
				Console.WriteLine($"Url {url}, Thread Id: {Thread.CurrentThread.ManagedThreadId}, Status: {reply.Status}");
				if(reply.Status != IPStatus.Success) 
				{
					
					Interlocked.Increment(ref failed);
					throw new TimeoutException($"Unable to reach {url}");
				}
			}
			catch(PingException e)
			{
				Console.WriteLine($"Url {url}, Thread Id: {Thread.CurrentThread.ManagedThreadId}, Status: {e.Message}");
				Interlocked.Increment(ref failed);
				throw;
			}
		}));		
	}
	
	return tasks;
}

WhenAny

  • Creates a task that will complete when any of the supplied tasks have completed.
  • Preferred over WaitAny
  • The returned task will always end in the RanToCompletion state even if the first task to complete ended in the Canceled or Faulted state.
private static void WhenAnyForTasks()
{
	List<Task> tasks = PingWebsites();
	Task t = Task.WhenAny(tasks);
	try
	{
		t.Wait();
	}
	catch {}
	
	if(t.Status == TaskStatus.RanToCompletion)
		Console.WriteLine("Any task completed");
	else
		Console.WriteLine("{0} tasks failed", failed);
}

Full Example