为什么我不能在lock语句的主体中使用'await'运算符?

497
在C#(.NET Async CTP)中,await关键字不允许在lock语句块内使用。
根据MSDN的说明: await表达式不能用于同步函数、查询表达式、异常处理语句的catch或finally块、lock语句块内部或不安全上下文中。
我想这对编译器团队来说可能难以实现,或者因为某些原因是不可能的。
我尝试使用using语句来解决此问题:
class Async
{
    public static async Task<IDisposable> Lock(object obj)
    {
        while (!Monitor.TryEnter(obj))
            await TaskEx.Yield();

        return new ExitDisposable(obj);
    }

    private class ExitDisposable : IDisposable
    {
        private readonly object obj;
        public ExitDisposable(object obj) { this.obj = obj; }
        public void Dispose() { Monitor.Exit(this.obj); }
    }
}

// example usage
using (await Async.Lock(padlock))
{
    await SomethingAsync();
}

然而,这并没有按照预期的方式工作。在ExitDisposable.Dispose中调用Monitor.Exit似乎会无限期地阻塞(大部分时间),导致其他线程尝试获取锁时出现死锁。我怀疑我的解决方法不可靠,并且await语句不允许在lock语句中的原因可能是相关的。

有人知道为什么lock语句内不允许使用await吗?


41
我想你已经找到了为什么不允许的原因。 - asawyer
3
我建议您查看这个链接:http://www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx 和这个链接:http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx。 - hans
我刚开始追赶并学习一些关于异步编程的知识。在我的WPF应用程序中遇到了许多死锁问题后,我发现这篇文章是异步编程实践中的一个很好的安全保障。https://msdn.microsoft.com/en-us/magazine/jj991977.aspx?f=255&MSPPError=-2147217396 - C. Tewalt
3
锁是为了防止异步访问损坏代码而设计的,因此如果你在锁内部使用异步,你已经使锁失效了。因此,如果你需要在锁内等待某些内容,那么你就没有正确使用锁。请注意,翻译后的文本不得改变原意,也不得增加解释。 - MikeT
1
blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx已经失效,我相信它应该是https://devblogs.microsoft.com/pfxteam/building-async-coordination-primitives-part-6-asynclock/和https://devblogs.microsoft.com/pfxteam/building-async-coordination-primitives-part-7-asyncreaderwriterlock/。 - prime23
在这里,您可以找到一个可能适合您的解决方案:https://blog.cdemi.io/async-waiting-inside-c-sharp-locks/ - peter70
9个回答

499

我认为编译器团队之所以无法实现这一点,要么是因为很难实现,要么是因为不可能实现。

不,实现这个并不难或者不可能 -- 你自己已经实现了这一点就证明了这个事实。相反,这是一个极其糟糕的想法,我们不允许它的存在,以保护您不会犯这个错误。

在ExitDisposable.Dispose中调用Monitor.Exit似乎会无限期地阻塞(大部分时间),从而导致死锁,因为其他线程试图获取锁。我怀疑我的解决方法的不可靠性以及await语句不允许在lock语句中使用的原因可能存在某种联系。

没错,你已经发现了我们为什么要将其禁止的原因。在锁内部等待就是制造死锁的方法。

我相信你能理解原因:等待返回在方法恢复操作之前会运行任意代码。那些任意代码可能会取出产生锁顺序倒置而导致死锁的锁。

更糟糕的是,在其他线程上恢复代码(在高级方案中;通常你会在进行等待的那个线程上继续运行,但不一定)这种情况下,解锁将解锁不同于获取锁的线程上的锁。好主意吗?不是。

我注意到在lock语句内部执行yield return也是一种“最糟糕的做法”,原因是相同的。尽管这样是合法的,但我希望我们能够将其禁止。对于“await”我们不会犯同样的错误。


276
当需要返回缓存条目,但该条目不存在时,您需要异步计算内容并添加条目,然后返回该条目,同时确保在此期间没有其他人调用您的情况下,应如何处理? - Softlion
16
我意识到我来晚了,但是我很惊讶地看到你将死锁作为这个想法不好的主要原因。在我的思考中,我得出结论,锁/监视器的可重入性会更大程度上造成问题。也就是说,您将两个需要锁定的任务排队到线程池中,在同步世界中这两个任务会在不同的线程上执行。但是现在,通过await(如果允许的话),由于线程被重复使用,你可以有两个任务在lock块内执行。然后,一切都变得混乱无比。或者我对此有误解吗? - Gareth Wilson
5
@GarethWilson:我谈论死锁是因为问题是关于死锁的。你说得对,奇怪的重入问题是可能的,而且似乎很有可能发生。 - Eric Lippert
16
考虑到SemaphoreSlim.WaitAsync类是在您发布此答案之后加入到.NET框架中的,我们可以安全地假设现在它是可能的。尽管如此,您对实现这样的结构的困难性的评论仍然完全有效。 - Contango
16
“任意代码在await返回控制权给调用者和方法恢复之间运行” - 这对于任何代码来说都是正确的,即使在没有异步/等待的多线程环境中也是如此:其他线程可以随时执行任意代码,并且正如你所说的那样,“可能会取出产生锁定顺序反转和因此导致死锁的锁定”。那么为什么这在异步/等待中特别重要呢?我理解第二点关于“代码可能会在另一个线程上恢复”对于异步/等待来说尤为重要。 - bacar
显示剩余10条评论

445

使用SemaphoreSlim.WaitAsync 方法。

 await mySemaphoreSlim.WaitAsync();
 try {
     await Stuff();
 } finally {
     mySemaphoreSlim.Release();
 }

22
由于这种方法最近被引入到.NET框架中,我认为我们可以假设在异步/等待世界中锁定的概念现在已经得到了很好的证明。 - Contango
9
若想了解更多信息,请在此文章中搜索“SemaphoreSlim”文本: 异步/等待 - 异步编程的最佳实践 - BobbyA
2
@JamesKo 如果所有这些任务都在等待“Stuff”的结果,我看不到任何绕过它的方法... - Ohad Schneider
35
为了像lock(...)一样工作,应该将其初始化为 mySemaphoreSlim = new SemaphoreSlim(1, 1) - Serg
6
添加了这个答案的扩展版本:https://dev59.com/Z2sz5IYBdhLWcg3w3btb#50139704 - Serg
显示剩余5条评论

87

这只是对用户1639030的答案的扩展。


基础版本


using System;
using System.Threading;
using System.Threading.Tasks;

public class SemaphoreLocker
{
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

    public async Task LockAsync(Func<Task> worker)
    {
        await _semaphore.WaitAsync();
        try
        {
            await worker();
        }
        finally
        {
            _semaphore.Release();
        }
    }

    // overloading variant for non-void methods with return type (generic T)
    public async Task<T> LockAsync<T>(Func<Task<T>> worker)
    {
        await _semaphore.WaitAsync();
        try
        {
            return await worker();
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

用法:

public class Test
{
    private static readonly SemaphoreLocker _locker = new SemaphoreLocker();

    public async Task DoTest()
    {
        await _locker.LockAsync(async () =>
        {
            // [async] calls can be used within this block 
            // to handle a resource by one thread. 
        });
        // OR
        var result = await _locker.LockAsync(async () =>
        {
            // [async] calls can be used within this block 
            // to handle a resource by one thread. 
        });
    }
}

扩展版本


这是一个声称完全避免死锁的LockAsync方法的版本(来自Jez建议的第四版修订)。

using System;
using System.Threading;
using System.Threading.Tasks;

public class SemaphoreLocker
{
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

    public async Task LockAsync(Func<Task> worker)
    {
        var isTaken = false;
        try
        {
            do
            {
                try
                {
                }
                finally
                {
                    isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
                }
            }
            while (!isTaken);
            await worker();
        }
        finally
        {
            if (isTaken)
            {
                _semaphore.Release();
            }
        }
    }

    // overloading variant for non-void methods with return type (generic T)
    public async Task<T> LockAsync<T>(Func<Task<T>> worker)
    {
        var isTaken = false;
        try
        {
            do
            {
                try
                {
                }
                finally
                {
                    isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
                }
            }
            while (!isTaken);
            return await worker();
        }
        finally
        {
            if (isTaken)
            {
                _semaphore.Release();
            }
        }
    }
}

用法:

public class Test
{
    private static readonly SemaphoreLocker _locker = new SemaphoreLocker();

    public async Task DoTest()
    {
        await _locker.LockAsync(async () =>
        {
            // [async] calls can be used within this block 
            // to handle a resource by one thread. 
        });
        // OR
        var result = await _locker.LockAsync(async () =>
        {
            // [async] calls can be used within this block 
            // to handle a resource by one thread. 
        });
    }
}

5
try块外获取信号量锁可能会很危险 - 如果在WaitAsynctry之间发生异常,信号量将永远不会被释放(死锁)。另一方面,将WaitAsync调用移动到try块中会引入另一个问题,即可以在未获得锁的情况下释放信号量。请参见相关线程,其中解释了此问题:https://dev59.com/7GAf5IYBdhLWcg3w7mQ_#61806749 - AndreyCh
3
谢谢参与,@Nikolai。你是正确的,但是我已经有一年多没有使用async/await了,因为我改变了我的技术栈。顺便问一下,你认为 @AndreyCh 的评论怎么样?我真的没时间去仔细看他的评论并回复。 - Serg
2
@Jez 我认为你的修改(第4次修订)与原始答案偏离太远。我建议你将其作为单独的答案发布,以便可以根据其自身的价值进行评估(投票)。 - Theodor Zoulias
1
@Jez([您的版本](https://stackoverflow.com/revisions/50139704/4))有更多的代码,这些代码存在于不明显的原因,并引入了不明显的性能影响。并且没有任何解释/文档。这是一个五年前的答案,因其实用性和简单性而得到积极评价。如果一开始就像您建议的那样复杂,它将不会有当前赞数的十分之一。欢迎您通过发布单独的答案来证明我是错误的。 - Theodor Zoulias
1
伙计们,为了停止评论战争,我建议发表双方观点,让读者自行选择。 - Serg
显示剩余16条评论

80

基本上这是错误的做法。

这里有两种实现方法:

  • 在代码块结束前一直持有锁
    这是一个非常糟糕的想法,因为你不知道异步操作要花多长时间。你应该尽量保持锁定时间的最小化。而且这可能是不可能的,因为一个线程拥有锁,而不是一个方法 - 根据任务调度程序,您甚至可能不会在同一线程上执行异步方法的其余部分。

  • 在 await 中释放锁,并在 await 返回时重新获取锁
    在我看来,这违反了最少惊讶原则,在异步方法行为应尽可能像等效的同步代码 - 除非您在 lock 块中使用 Monitor.Wait,否则您希望持有锁以保证代码块的执行。

因此,基本上这里存在两个竞争的要求-你不应该试图在这里采用第一种方法,如果你想采用第二种方法,你可以通过在 await 表达式之前和之后分别添加两个分离的 lock 块来使代码更加清晰。

// Now it's clear where the locks will be acquired and released
lock (foo)
{
}
var result = await something;
lock (foo)
{
}

因此,通过禁止您在锁定块本身中等待,语言强制您思考您真正想要做什么,并使您编写的代码中的选择更清晰。


6
鉴于您发布这个答案后,.NET框架添加了SemaphoreSlim.WaitAsync类,我们可以安全地假设现在是可能的。不过,即使如此,您关于实现这种构造的难度的评论仍然是完全正确的。 - Contango
9
@Contango:好吧,那不是完全相同的事情。特别地,信号量并不绑定到特定的线程。它实现了与锁类似的目标,但有显著的差异。 - Jon Skeet
@JonSkeet 我知道这是一个非常古老的线程,但是我不确定在第二种方式中如何使用那些锁保护something()调用?当一个线程执行something()时,任何其他线程也可以参与其中!我是否遗漏了什么? - user5699258
@Joseph:在那个时候它并没有受到保护。这是第二种方法,它清楚地表明你正在获取/释放,然后再次获取/释放,可能在不同的线程上。因为根据Eric的回答,第一种方法是一个坏主意。 - Jon Skeet

20
这涉及到构建异步协调原语,第6部分:AsyncLockhttp://winrtstoragehelper.codeplex.com/、Windows 8应用商店和.NET 4.5。
在使用async/await语言功能时,许多事情变得相当容易,但它也引入了一种以前很少遇到的情况:可重入性。
对于事件处理程序特别适用,因为对于许多事件,您在从事件处理程序返回后不知道发生了什么。 一个可能真正发生的事情是,在第一个事件处理程序中等待的异步方法会从另一个事件处理程序中仍然在同一个线程上调用。
以下是我在Windows 8应用商店应用程序中遇到的一个真实场景: 我的应用程序有两个框架:进入和离开一个框架时,我想将一些数据加载/安全保存到文件/存储中。 OnNavigatedTo/From事件用于保存和加载。保存和加载由某些异步实用函数完成(例如http://winrtstoragehelper.codeplex.com/)。 当从框架1导航到框架2或反之亦然时,将调用异步加载和安全操作并等待。 事件处理程序变为异步返回void =>它们不能被等待。
但是,实用程序的第一个文件打开操作(比如说:在保存函数内部)也是异步的,因此第一个await返回控件到框架,稍后通过第二个事件处理程序调用另一个实用程序(加载)。 现在,如果文件已经由保存操作打开,则尝试打开相同的文件进行加载,并失败并出现ACCESSDENIED异常。
对于我来说,最小化的解决方案是通过使用和AsyncLock来保护文件访问。
private static readonly AsyncLock m_lock = new AsyncLock();
...

using (await m_lock.LockAsync())
{
    file = await folder.GetFileAsync(fileName);
    IRandomAccessStream readStream = await file.OpenAsync(FileAccessMode.Read);
    using (Stream inStream = Task.Run(() => readStream.AsStreamForRead()).Result)
    {
        return (T)serializer.Deserialize(inStream);
    }
}

请注意,该锁基本上使用一个锁来锁定实用程序的所有文件操作,这是不必要的强制措施,但对于我的情况效果很好。这里是我的测试项目:Windows 8应用商店应用程序,其中包含一些来自http://winrtstoragehelper.codeplex.com/原始版本和我的修改版本的测试调用,它使用了Stephen Toub的AsyncLock。我还可以建议您查看此链接:http://www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx

11

Stephen Taub已经实现了这个问题的解决方案,详见Building Async Coordination Primitives, Part 7: AsyncReaderWriterLock

Stephen Taub在业界备受推崇,因此他所写的任何内容都很可靠。

我不会复制他在博客上发布的代码,但我会向您展示如何使用它:

/// <summary>
///     Demo class for reader/writer lock that supports async/await.
///     For source, see Stephen Taub's brilliant article, "Building Async Coordination
///     Primitives, Part 7: AsyncReaderWriterLock".
/// </summary>
public class AsyncReaderWriterLockDemo
{
    private readonly IAsyncReaderWriterLock _lock = new AsyncReaderWriterLock(); 

    public async void DemoCode()
    {           
        using(var releaser = await _lock.ReaderLockAsync()) 
        { 
            // Insert reads here.
            // Multiple readers can access the lock simultaneously.
        }

        using (var releaser = await _lock.WriterLockAsync())
        {
            // Insert writes here.
            // If a writer is in progress, then readers are blocked.
        }
    }
}

如果你想要一个内置于.NET框架中的方法,可以使用SemaphoreSlim.WaitAsync。虽然它不能提供读写锁,但确保了经过验证的实现。


我很好奇使用这段代码是否有任何注意事项。如果有人能够演示此代码存在的任何问题,我想知道。然而,真正的情况是,异步/等待锁定的概念绝对是经过充分验证的,因为SemaphoreSlim.WaitAsync在.NET框架中。这段代码所做的就是添加了一个读者/写者锁概念。 - Contango
1
一个读者/写者的概念非常有用。这种东西应该内置在.NET中。拥有框架的整个原因不就是让你依赖它为你提供这些基本元素之类的有用东西吗? - Jez

1

嗯,看起来很丑,但似乎能正常工作。

static class Async
{
    public static Task<IDisposable> Lock(object obj)
    {
        return TaskEx.Run(() =>
            {
                var resetEvent = ResetEventFor(obj);

                resetEvent.WaitOne();
                resetEvent.Reset();

                return new ExitDisposable(obj) as IDisposable;
            });
    }

    private static readonly IDictionary<object, WeakReference> ResetEventMap =
        new Dictionary<object, WeakReference>();

    private static ManualResetEvent ResetEventFor(object @lock)
    {
        if (!ResetEventMap.ContainsKey(@lock) ||
            !ResetEventMap[@lock].IsAlive)
        {
            ResetEventMap[@lock] =
                new WeakReference(new ManualResetEvent(true));
        }

        return ResetEventMap[@lock].Target as ManualResetEvent;
    }

    private static void CleanUp()
    {
        ResetEventMap.Where(kv => !kv.Value.IsAlive)
                     .ToList()
                     .ForEach(kv => ResetEventMap.Remove(kv));
    }

    private class ExitDisposable : IDisposable
    {
        private readonly object _lock;

        public ExitDisposable(object @lock)
        {
            _lock = @lock;
        }

        public void Dispose()
        {
            ResetEventFor(_lock).Set();
        }

        ~ExitDisposable()
        {
            CleanUp();
        }
    }
}

0
我创建了一个名为MutexAsyncable的类,受到Stephen Toub的AsyncLock实现(在这篇博客文章中讨论)的启发,它可以用作同步或异步代码中lock语句的替代品。
using System;
using System.Threading;
using System.Threading.Tasks;

namespace UtilsCommon.Lib;

/// <summary>
/// Class that provides (optionally async-safe) locking using an internal semaphore.
/// Use this in place of a lock() {...} construction.
/// Bear in mind that all code executed inside the worker must finish before the next
/// thread is able to start executing it, so long-running code should be avoided inside
/// the worker if at all possible.
///
/// Example usage for sync:
/// using (mutex.LockSync()) {
///     // ... code here which is synchronous and handles a shared resource ...
///     return[ result];
/// }
///
/// ... or for async:
/// using (await mutex.LockAsync()) {
///     // ... code here which can use await calls and handle a shared resource ...
///     return[ result];
/// }
/// </summary>
public sealed class MutexAsyncable {
    #region Internal classes

    private sealed class Releaser : IDisposable {
        private readonly MutexAsyncable _toRelease;
        internal Releaser(MutexAsyncable toRelease) { _toRelease = toRelease; }
        public void Dispose() { _toRelease._semaphore.Release(); }
    }

    #endregion

    private readonly SemaphoreSlim _semaphore = new(1, 1);
    private readonly Task<IDisposable> _releaser;

    public MutexAsyncable() {
        _releaser = Task.FromResult((IDisposable)new Releaser(this));
    }

    public IDisposable LockSync() {
        _semaphore.Wait();
        return _releaser.Result;
    }

    public Task<IDisposable> LockAsync() {
        var wait = _semaphore.WaitAsync();
        if (wait.IsCompleted) { return _releaser; }
        else {
            // Return Task<IDisposable> which completes once WaitAsync does
            return wait.ContinueWith(
                (_, state) => (IDisposable)state!,
                _releaser.Result,
                CancellationToken.None,
                TaskContinuationOptions.ExecuteSynchronously,
                TaskScheduler.Default
            );
        }
    }
}

如果您使用的是.NET 5+,那么使用上述内容是安全的,因为它不会抛出ThreadAbortException

我还创建了一个扩展的SemaphoreLocker类,受this answer启发,它可以作为lock的通用替代品,可同步或异步使用。它比上面的MutexAsyncable效率低,并且分配更多资源,但它有一个好处,即强制工作代码在完成后释放锁(从技术上讲,MutexAsyncable返回的IDisposable可能无法通过调用代码进行处理并导致死锁)。它还有额外的try/finally代码来处理可能出现的ThreadAbortException,因此应该可以在早期的.NET版本中使用:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace UtilsCommon.Lib;

/// <summary>
/// Class that provides (optionally async-safe) locking using an internal semaphore.
/// Use this in place of a lock() {...} construction.
/// Bear in mind that all code executed inside the worker must finish before the next thread is able to
/// start executing it, so long-running code should be avoided inside the worker if at all possible.
///
/// Example usage:
/// [var result = ]await _locker.LockAsync(async () => {
///     // ... code here which can use await calls and handle a shared resource one-thread-at-a-time ...
///     return[ result];
/// });
///
/// ... or for sync:
/// [var result = ]_locker.LockSync(() => {
///     // ... code here which is synchronous and handles a shared resource one-thread-at-a-time ...
///     return[ result];
/// });
/// </summary>
public sealed class SemaphoreLocker : IDisposable {
    private readonly SemaphoreSlim _semaphore = new(1, 1);

    /// <summary>
    /// Runs the worker lambda in a locked context.
    /// </summary>
    /// <typeparam name="T">The type of the worker lambda's return value.</typeparam>
    /// <param name="worker">The worker lambda to be executed.</param>
    public T LockSync<T>(Func<T> worker) {
        var isTaken = false;
        try {
            do {
                try {
                }
                finally {
                    isTaken = _semaphore.Wait(TimeSpan.FromSeconds(1));
                }
            }
            while (!isTaken);
            return worker();
        }
        finally {
            if (isTaken) {
                _semaphore.Release();
            }
        }
    }

    /// <inheritdoc cref="LockSync{T}(Func{T})" />
    public void LockSync(Action worker) {
        var isTaken = false;
        try {
            do {
                try {
                }
                finally {
                    isTaken = _semaphore.Wait(TimeSpan.FromSeconds(1));
                }
            }
            while (!isTaken);
            worker();
        }
        finally {
            if (isTaken) {
                _semaphore.Release();
            }
        }
    }

    /// <summary>
    /// Runs the worker lambda in an async-safe locked context.
    /// </summary>
    /// <typeparam name="T">The type of the worker lambda's return value.</typeparam>
    /// <param name="worker">The worker lambda to be executed.</param>
    public async Task<T> LockAsync<T>(Func<Task<T>> worker) {
        var isTaken = false;
        try {
            do {
                try {
                }
                finally {
                    isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
                }
            }
            while (!isTaken);
            return await worker();
        }
        finally {
            if (isTaken) {
                _semaphore.Release();
            }
        }
    }

    /// <inheritdoc cref="LockAsync{T}(Func{Task{T}})" />
    public async Task LockAsync(Func<Task> worker) {
        var isTaken = false;
        try {
            do {
                try {
                }
                finally {
                    isTaken = await _semaphore.WaitAsync(TimeSpan.FromSeconds(1));
                }
            }
            while (!isTaken);
            await worker();
        }
        finally {
            if (isTaken) {
                _semaphore.Release();
            }
        }
    }

    /// <summary>
    /// Releases all resources used by the current instance of the SemaphoreLocker class.
    /// </summary>
    public void Dispose() {
        _semaphore.Dispose();
    }
}

代码中包含一些很长的注释,导致出现了水平滚动条,使得代码难以阅读。我建议通过添加一些换行来缩短注释。 - Theodor Zoulias
我建议在 await worker() 中添加 Configure.Await(false)。同时,解释一下为什么需要 TimeSpan.FromSeconds(1) 和神秘的空 try/finally 块会更好。此外,对于 TimeSpan.FromSeconds(1) 的性能影响,例如在有数千个执行流挂起在 await LockAsync 命令时,提出一些警告也是不错的选择。 - Theodor Zoulias
为什么会有数千个被挂起的执行流?听起来像是你的程序在这种情况下会有更大的问题。 - Jez
你认为AsyncEx的AsyncLock是更好的替代品吗? - Jez
1
我已经更新了我的答案,添加了一个希望更高效的 MutexAsyncable 类,适用于 .NET 5+。 - Jez
显示剩余10条评论

-1

我尝试使用下面的Monitor代码,它似乎可以工作,但有一个陷阱...当你有多个线程时,它会出现以下错误:

System.Threading.SynchronizationLockException 从未同步的代码块中调用了对象同步方法。

using System;
using System.Threading;
using System.Threading.Tasks;

namespace MyNamespace
{
    public class ThreadsafeFooModifier : 
    {
        private readonly object _lockObject;

        public async Task<FooResponse> ModifyFooAsync()
        {
            FooResponse result;
            Monitor.Enter(_lockObject);
            try
            {
                result = await SomeFunctionToModifyFooAsync();
            }
            finally
            {
                Monitor.Exit(_lockObject);
            }
            return result;
        }
    }
}

在此之前,我只是这样做,但由于在ASP.NET控制器中执行,因此导致了死锁。
public async Task<FooResponse> ModifyFooAsync()
{
    lock(lockObject)
    {
        return SomeFunctionToModifyFooAsync.Result;
    }
}

请注意:以下代码看起来可能有效,但最终会给你带来麻烦。因此,请不要直接使用这段代码,而是复制并使用它!SemaphoreSlim 是应该使用的,但不要忘记正确地处理它的释放。 - andrew pate

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接