'Calling Entity Framework async code, synchronously, within a lock

I have an async method which will load some info from the database via Entity Framework.

In one circumstance I want to call that code synchronously from within a lock.

Do I need two copies of the code, one async, one not, or is there a way of calling the async code synchronously?

For example something like this:

using System;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {
        new Test().Go();
    }
}

public class Test
{
    private object someLock = new object();

    public void Go()
    {
        lock(someLock)
        {
            Task<int> task = Task.Run(async () => await DoSomethingAsync()); 
            var result = task.Result;
        }
    }
    
    public async Task<int> DoSomethingAsync()
    {
        // This will make a database call
        return await Task.FromResult(0);
    }
}

Edit: as a number of the comments are saying the same thing, I thought I'd elaborate a little

Background: normally, trying to do this is a bad idea. Lock and async are polar opposites as documented in lots of places, there's no reason to have an async call in a lock.

So why do it here? I can make the database call synchronously but that requires duplicating some methods which isn't ideal. Ideally the language would let you call the same method synchronously or asynchronously

Scenario: this is a Web API. The application starts, a number of Web API calls execute and they all want some info that's in the database that's provided by a service provider dedicated for that purpose (i.e. a call added via AddScoped in the Startup.cs). Without something like a lock they will all try to get the info from the database. EF Core is only relevant in that every other call to the database is async, this one is the exception.



Solution 1:[1]

You simply cannot use a lock with asynchronous code; the entire point of async/await is to switch away from a strict thread-based model, but lock aka System.Monitor is entirely thread focused. Frankly, you also shouldn't attempt to synchronously call asynchronous code; that is simply not valid, and no "solution" is correct.

SemaphoreSlim makes a good alternative to lock as an asynchronous-aware synchronization primitve. However, you should either acquire/release the semaphore inside the async operation in your Task.Run, or you should make your Go an asynchronous method, i.e. public async Task GoAsync(), and do the same there; of course, at that point it becomes redundant to use Task.Run, so: just execute await DoSomethingAsync() directly:

private readonly SemaphoreSlim someLock = new SemaphoreSlim(1, 1);
public async Task GoAsync()
{
    await someLock.WaitAsync();
    try
    {
        await DoSomethingAsync();
    }
    finally
    {
        someLock.Release();
    }
}

If the try/finally bothers you; perhaps cheat!

public async Task GoAsync()
{
    using (await someLock.LockAsync())
    {
        await DoSomethingAsync();
    }
}

with

internal static class SemaphoreExtensions
{
    public static ValueTask<SemaphoreToken> LockAsync(this SemaphoreSlim semaphore)
    {
        // try to take synchronously
        if (semaphore.Wait(0)) return new(new SemaphoreToken(semaphore));

        return SlowLockAsync(semaphore);

        static async ValueTask<SemaphoreToken> SlowLockAsync(SemaphoreSlim semaphore)
        {
            await semaphore.WaitAsync().ConfigureAwait(false);
            return new(semaphore);
        }
    }
}
internal readonly struct SemaphoreToken : IDisposable
{
    private readonly SemaphoreSlim _semaphore;
    public void Dispose() => _semaphore?.Release();
    internal SemaphoreToken(SemaphoreSlim semaphore) => _semaphore = semaphore;
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1