Waitgroups in C#

Our example demonstrates how to wait for multiple tasks to finish using the Task class and Task.WhenAll method in C#.

using System;
using System.Threading.Tasks;

class WaitGroups
{
    // This is the method we'll run in every task.
    static async Task Worker(int id)
    {
        Console.WriteLine($"Worker {id} starting");

        // Simulate an expensive task.
        await Task.Delay(TimeSpan.FromSeconds(1));
        Console.WriteLine($"Worker {id} done");
    }

    static async Task Main()
    {
        // Create a list to hold our tasks
        var tasks = new List<Task>();

        // Launch several tasks
        for (int i = 1; i <= 5; i++)
        {
            int id = i;
            tasks.Add(Task.Run(() => Worker(id)));
        }

        // Wait for all tasks to complete
        await Task.WhenAll(tasks);

        // Note: This approach automatically propagates exceptions from tasks.
        // For more advanced use cases, you might want to consider using
        // the TPL Dataflow library or other concurrent programming patterns.
    }
}

To run the program:

$ dotnet run
Worker 1 starting
Worker 3 starting
Worker 4 starting
Worker 2 starting
Worker 5 starting
Worker 4 done
Worker 1 done
Worker 2 done
Worker 5 done
Worker 3 done

The order of workers starting up and finishing is likely to be different for each invocation.

In this C# version, we use the Task class to represent asynchronous operations, which is similar to goroutines in Go. The Task.WhenAll method is used to wait for all tasks to complete, which is analogous to the WaitGroup.Wait() in Go.

Instead of explicitly adding to and decrementing a counter like with Go’s WaitGroup, we add each task to a list and then wait for all of them to complete using Task.WhenAll.

The async and await keywords in C# allow us to write asynchronous code in a more straightforward manner, similar to how Go’s goroutines simplify concurrent programming.

Note that C#’s approach automatically propagates exceptions from tasks, which addresses the limitation mentioned in the Go example about error propagation.