如何将“锁定”重构为“异步/等待”?

Zac*_*ach 3 c# thread-synchronization async-await

我们有一个用 C# 目标框架 2.0 编写的旧库。最近我们将在现代 .net core 项目中使用它并打算使用async/await. 然而,旧图书馆有很多lock街区。

我们计划添加新async方法来实现相同的逻辑。

例如,

旧代码

void GetOrder()
{
    // ...
    lock(_lock)
    {
    //...
    }
}
Run Code Online (Sandbox Code Playgroud)

预期结果

async Task AsyncGetOrder()
{
    // ...
    await DoSomethingWithLock()
}
Run Code Online (Sandbox Code Playgroud)

请给我一些关于如何翻译lock成 的建议async/await

Mat*_*mas 7

有以下几种通用方法:

#1 重构时根本不需要锁

四处寻找如何编写无锁 C#有时它涉及对类Interlocked明智使用。其他时候,它涉及到心态向不可变状态的转变(询问函数式程序员)。在很多情况下,这样做可以显着提高并行性能。

当然,无锁线程安全代码可以同步或异步执行。

#2 重构,不再需要可重入锁,然后与不允许重入的内容进行异步

这基本上就是人们在推荐 NuGet 包或类似包时所推荐的SemaphoreSlim内容AsyncEx

斯蒂芬·克利里 (Stephen Cleary) 撰写了有关走这条路线背后的智慧的文章。他还给出了一些如何做到这一点的例子:

https://blog.stephencleary.com/2013/04/recursive-re-entrant-locks.html

Monitor.Lock#3 找到/的异步替代品lock

基本上你需要的是同时提供所有这三个功能的东西:

  • 异步性
  • 再入
  • 相互排斥

Monitor.Lock/lock给你第二件事和第三件事。所以你想要的东西既能给你第一个,又不牺牲其他两个。

这比看起来有点棘手。乍一看,有几个 NuGet 包似乎可以执行此操作。但我所知道的唯一正确的是这个 NuGet 包(我写的):

https://www.nuget.org/packages/ReentrantAsyncLock/

这是在行动中:

var asyncLock = new ReentrantAsyncLock();
var raceCondition = 0;
// You can acquire the lock asynchronously
await using (await asyncLock.LockAsync(CancellationToken.None))
{
    await Task.WhenAll(
        Task.Run(async () =>
        {
            // The lock is reentrant
            await using (await asyncLock.LockAsync(CancellationToken.None))
            {
                // The lock provides mutual exclusion
                raceCondition++;
            }
        }),
        Task.Run(async () =>
        {
            await using (await asyncLock.LockAsync(CancellationToken.None))
            {
                raceCondition++;
            }
        })
    );
}
Assert.Equal(2, raceCondition);
Run Code Online (Sandbox Code Playgroud)

这当然不是第一次尝试这样做。但正如我所说,这是迄今为止我见过的唯一正确的尝试。其他一些实现会在尝试在其中一个调用中重新输入锁时发生死锁 Task.Run。其他的实际上不会提供互斥,并且 raceCondition变量有时会等于 1 而不是 2: