.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;
}
}
}