As the famous blog post from Stephen Cleary dictates, one should never try to run async code synchronously (e.g. via Task.RunSynchronously()
or accessing Task.Result
). On the other hand, you can't use async/await inside lock
statement.
My use case is ASP.NET Core app, which uses IMemoryCache
to cache some data. Now when the data is not available, (e.g. cache is dropped) I have to repopulate it, and that should be guarded with lock
.
public TItem Get<TItem>(object key, Func<TItem> factory)
{
if (!_memoryCache.TryGetValue(key, out TItem value))
{
lock (_locker)
{
if (!_memoryCache.TryGetValue(key, out value))
{
value = factory();
Set(key, value);
}
}
}
return value;
}
In this example, the factory function can not be async! What should be done if it has to be async?
An easy way to coordinate asynchronous access to a shared variable is to use a SemaphoreSlim
. You call WaitAsync
to begin an asynchronous lock, and Release
to end it.
E.g.
private static readonly SemaphoreSlim _cachedCustomersAsyncLock = new SemaphoreSlim(1, 1);
private static ICollection<Customer> _cachedCustomers;
private async Task<ICollection<Customer>> GetCustomers()
{
if (_cachedCustomers is null)
{
await _cachedCustomersAsyncLock.WaitAsync();
try
{
if (_cachedCustomers is null)
{
_cachedCustomers = GetCustomersFromDatabase();
}
}
finally
{
_cachedCustomersAsyncLock.Release();
}
}
return _cachedCustomers;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With