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吗?


当前回答

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

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

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

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

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

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

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

其他回答

基本上这是错误的做法。

有两种实现方式:

Keep hold of the lock, only releasing it at the end of the block. This is a really bad idea as you don't know how long the asynchronous operation is going to take. You should only hold locks for minimal amounts of time. It's also potentially impossible, as a thread owns a lock, not a method - and you may not even execute the rest of the asynchronous method on the same thread (depending on the task scheduler). Release the lock in the await, and reacquire it when the await returns This violates the principle of least astonishment IMO, where the asynchronous method should behave as closely as possible like the equivalent synchronous code - unless you use Monitor.Wait in a lock block, you expect to own the lock for the duration of the block.

所以基本上这里有两个相互竞争的需求——你不应该尝试在这里做第一种方法,如果你想采用第二种方法,你可以通过使用await表达式分隔两个分离的锁块来使代码更加清晰:

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

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

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

使用SemaphoreSlim。WaitAsync方法。

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

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

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

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

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

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

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

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