Getting started

edit

Install the package

Add the Meziantou.Framework.Threading package to a project:

dotnet package add Meziantou.Framework.Threading

The types are in three namespaces: Meziantou.Framework.Threading, Meziantou.Framework.Threading.Tasks and Meziantou.Framework.Collections.Concurrent. See Introduction for what each contains.

Protect a resource in asynchronous code

The lock statement cannot contain an await. AsyncLock can: only one caller at a time runs the code inside the using block, and the others wait asynchronously.

var asyncLock = new AsyncLock();
var counter = 0;

async Task AccessResourceAsync()
{
    using (await asyncLock.LockAsync())
    {
        // Critical section - only one task can execute this at a time
        var value = counter;
        await Task.Yield();
        counter = value + 1;
    }
}

await Task.WhenAll(AccessResourceAsync(), AccessResourceAsync(), AccessResourceAsync());
Assert.Equal(3, counter);
  • LockAsync returns a lease once the lock is acquired. Disposing the lease, at the end of the using block, releases the lock.
  • Without the lock, the three calls could read the same value of counter before any of them writes it back.

Await several tasks and get their results

With the Meziantou.Framework.Threading.Tasks namespace, a tuple of tasks can be awaited directly. The result is a tuple of the results, which you can deconstruct:

var task1 = Task.FromResult(42);
var task2 = Task.FromResult("value");

var (a, b) = await (task1, task2);
Assert.Equal(42, a);
Assert.Equal("value", b);

var (c, d) = await (task1, task2).ConfigureAwait(false);
Assert.Equal(42, c);
Assert.Equal("value", d);

The tasks run concurrently: awaiting the tuple waits for all of them, like Task.WhenAll.

Next steps

  • Locks: reader-writer locks, TryLock, cancellation and SemaphoreSlim.
  • Awaiting tasks: ValueTask tuples, failures, and fire-and-forget.