c#中的await关键字。NET Async CTP)不允许在锁语句中使用。

从MSDN:

一个 Await表达式不能用于同步函数或查询中 表达式,在异常处理的catch或finally块中 语句,在锁语句的块中,或在不安全上下文中。

我认为由于某些原因,编译器团队很难或不可能实现这一点。

我尝试使用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();
}

然而,这并没有像预期的那样工作。对Monitor的调用。在ExitDisposable中退出。Dispose似乎无限期地阻塞(大多数时间),当其他线程试图获得锁时,会导致死锁。我怀疑我周围工作的不可靠性和原因等待语句不允许在锁语句以某种方式相关。

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


当前回答

我确实尝试使用一个监视器(下面的代码),它似乎可以工作,但有一个GOTCHA…当你有多个线程时,它会给出。对象同步方法从未同步的代码块调用。

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() { 锁(锁对象) { 返回 SomeFunctionToModifyFooAsync.Result; } }

其他回答

我确实尝试使用一个监视器(下面的代码),它似乎可以工作,但有一个GOTCHA…当你有多个线程时,它会给出。对象同步方法从未同步的代码块调用。

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() { 锁(锁对象) { 返回 SomeFunctionToModifyFooAsync.Result; } }

使用SemaphoreSlim。WaitAsync方法。

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

我认为由于某些原因,编译器团队很难或不可能实现这一点。

不,实现它一点也不困难或不可能——你自己实现它的事实证明了这一点。相反,这是一个非常糟糕的想法,所以我们不允许这样做,以保护你不犯这样的错误。

呼叫监视器。在ExitDisposable中退出。Dispose似乎无限期地阻塞(大多数时间),当其他线程试图获得锁时,会导致死锁。我怀疑我周围工作的不可靠性和原因等待语句不允许在锁语句以某种方式相关。

没错,你已经知道我们为什么把它定为非法了。在锁内等待是产生死锁的一种方法。

我相信您可以看到原因:在await将控制返回给调用者和方法恢复之间运行任意代码。这种任意代码可能会删除产生锁序反转的锁,从而导致死锁。

更糟糕的是,代码可能在另一个线程上恢复(在高级场景中;通常情况下,您将再次在执行await的线程上进行操作,但不一定),在这种情况下,unlock将在与取出锁的线程不同的线程上解锁锁。这是个好主意吗?不。

我注意到,出于同样的原因,在锁内部进行yield - return也是一种“最糟糕的做法”。这样做是合法的,但我希望我们把它定为非法。我们不会在await上犯同样的错误。

Stephen Taub已经实现了这个问题的解决方案,请参见构建异步协调原语,第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代替。您不会得到读取器/写入器锁,但是您将得到经过尝试和测试的实现。

嗯,看起来很丑,但似乎有用。

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