• Code Snippet
  • Async/Await
  • Locking
  • C#
  • .NET
  • SignalR

.NET locking in an Async Method

This snippet is for initializing a SignalR connection, I found this pattern to work really well for my use case. Since I want only one connection to my external service started the lock makes sure multiple calls to GetConnection will return a single initialized instances also making it lazily started. The pattern is simple enough, but could be enhanced with a key/value cache on the URL, so multiple connections can be cached.

namespace EventHorizon.Connection
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Http.Connections.Client;
    using Microsoft.AspNetCore.SignalR.Client;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Logging;

    public class ConnectionCache
    {
        // Here is our lock
        // Only one lock can be granted and a max of one lock
        private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);

        private readonly ILogger _logger;
        // Internal State
        private HubConnection? _connection;

        // Removed for brevity

        public async Task<HubConnection> GetConnection(
            string url
        )
        {
            if (_connection == null)
            {
                // Here we grab the lock.
                // Since we configured this to only allow one lock
                // this will wait till it is released.
                await _lock.WaitAsync();
                try
                {
                    // We check the _connection again after the lock.
                    // We do this since another call can create it.
                    if (_connection != null)
                    {
                        return _connection;
                    }
                    _connection = new HubConnectionBuilder()
                        .WithUrl(
                            "https://that.place.on.the.internet/hub"
                        )
                        .Build();
                    await _connection.StartAsync();
                }
                catch (Exception ex)
                {
                    // A little cleanup
                    _connection = null;
                    throw;
                }
                finally
                {
                    // Always release the lock, error or success.
                    _lock.Release();
                }
            }
            return _connection;
        }
    }
}

Cody's logo image, it is an abstract of a black hole with a white Event Horizon.

Cody Merritt Anhorn

A Engineer with a passion for Platform Architecture and Tool Development.