Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to combine asynchrony with locking?

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?

like image 675
Titan Avatar asked May 30 '17 18:05

Titan


1 Answers

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;
}
like image 145
C. Augusto Proiete Avatar answered Nov 15 '22 17:11

C. Augusto Proiete