【问题标题】:Calling Entity Framework async code, synchronously, within a lock在锁内同步调用实体框架异步代码
【发布时间】:2022-01-27 21:16:00
【问题描述】:

我有一个异步方法,它将通过实体框架从数据库中加载一些信息。

在一种情况下,我想从锁中同步调用该代码。

我需要两份代码,一份是异步的,一份不需要,还是有办法同步调用异步代码?

例如这样的:

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

编辑:由于许多 cmets 都在说同样的话,我想我会详细说明一下

背景:通常,尝试这样做是个坏主意。 Lock 和 async 是相反的,正如许多地方所记录的那样,没有理由在 lock 中进行异步调用。

那么为什么要在这里做呢?我可以同步进行数据库调用,但这需要复制一些不理想的方法。理想情况下,该语言可以让您同步或异步调用相同的方法

场景:这是一个 Web API。应用程序启动,许多 Web API 调用执行,它们都需要数据库中的一些信息,这些信息由专门用于此目的的服务提供商提供(即通过 Startup.cs 中的 AddScoped 添加的调用)。如果没有锁之类的东西,他们都会尝试从数据库中获取信息。 EF Core 的相关性仅在于对数据库的所有其他调用都是异步的,这是一个例外。

【问题讨论】:

  • 这能回答你的问题吗? Calling async method synchronously
  • 我的建议是首先不要使用同步代码。你不能做一些重构来正确地做所有事情吗?
  • 你不能在锁中使用异步代码,它需要一个锁
  • 我不知道锁是否引入了一些我需要注意的微妙问题,这使得它与其他问题略有不同
  • @tony 我猜它不需要锁;它需要同步,并且您对使用lock 很熟悉 - 但是:它不是唯一的选择。

标签: c# async-await entity-framework-core


【解决方案1】:

您根本不能将lock 与异步代码一起使用; async/await 的全部意义在于远离严格的基于线程的模型,但lock 又名System.Monitor 完全以线程为中心。坦率地说,您也不应该尝试同步调用异步代码;这根本是无效的,没有“解决方案”是正确的。

SemaphoreSlim 可以很好地替代lock 作为异步感知同步原语。但是,您应该或者async 操作中获取/释放信号量Task.Run,或者您应该使您的Go 成为异步方法,即@ 987654331@,并在那里做同样的事情;当然,此时使用Task.Run就变得多余了,所以:直接执行await DoSomethingAsync()即可:

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

如果try/finally 困扰您;也许作弊!

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

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

【讨论】:

  • 这听起来像是一个 XY 问题。如果 OP 试图从多个线程访问 DbContext,或者试图“加速”批量操作,SemaphoreSlim 将无济于事
  • @PanagiotisKanavos 确实;共享数据库上下文(或连接)听起来是个坏主意,并且通常会导致内存无限增长(由于跟踪),以及严重受限的吞吐量(由于同步)
猜你喜欢
  • 2020-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-28
  • 2016-07-14
相关资源
最近更新 更多