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


基本上这是错误的做法。

有两种实现方式:

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

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


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

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

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

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

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

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

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


这参考了构建异步协调原语,第6部分:AsyncLock, http://winrtstoragehelper.codeplex.com/, Windows 8应用程序商店和。net 4.5

以下是我的观点:

async/await语言特性使许多事情变得相当简单,但它也引入了一个场景 很少遇到以前如此容易使用异步调用:重新进入。

对于事件处理程序来说尤其如此,因为对于许多事件,您不知道从事件处理程序返回后发生了什么。 可能发生的一件事是,你在第一个事件处理程序中等待的异步方法,从另一个事件处理程序中调用 相同的线程。

Here is a real scenario I came across in a windows 8 App store app: My app has two frames: coming into and leaving from a frame I want to load/safe some data to file/storage. OnNavigatedTo/From events are used for the saving and loading. The saving and loading is done by some async utility function (like http://winrtstoragehelper.codeplex.com/). When navigating from frame 1 to frame 2 or in the other direction, the async load and safe operations are called and awaited. The event handlers become async returning void => they cant be awaited.

然而,该实用程序的第一个文件打开操作(让我们说:在保存函数内)也是异步的 因此,第一个await将控制返回给框架,框架稍后通过第二个事件处理程序调用另一个实用程序(load)。 加载现在尝试打开相同的文件,如果 文件现在已经打开用于保存操作,失败并出现ACCESSDENIED异常。

对我来说,一个最小的解决方案是通过using和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


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

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

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代替。您不会得到读取器/写入器锁,但是您将得到经过尝试和测试的实现。


我确实尝试使用一个监视器(下面的代码),它似乎可以工作,但有一个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; } }


这只是这个答案的延伸。

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