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

本文翻译自:Why can't I use the 'await' operator within the body of a lock statement?

The await keyword in C# (.NET Async CTP) is not allowed from within a lock statement. 锁定语句中不允许使用C#(.NET Async CTP)中的await关键字。

From MSDN : MSDN

An await expression cannot be used in a synchronous function, in a query expression, in the catch or finally block of an exception handling statement, in the block of a lock statement , or in an unsafe context. 等待表达式不能用于同步函数,查询表达式,异常处理语句的catch或finally块, 锁语句的块或不安全的上下文中。

I assume this is either difficult or impossible for the compiler team to implement for some reason. 我认为由于某种原因,对于编译器团队而言,这既困难又不可能。

I attempted a work around with the using statement: 我尝试了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();
}

However this does not work as expected. 但是,这无法正常工作。 The call to Monitor.Exit within ExitDisposable.Dispose seems to block indefinitely (most of the time) causing deadlocks as other threads attempt to acquire the lock. 在ExitDisposable.Dispose中对Monitor.Exit的调用似乎无限期地(大部分时间)阻塞,导致死锁,因为其他线程试图获取该锁。 I suspect the unreliability of my work around and the reason await statements are not allowed in lock statement are somehow related. 我怀疑我的工作不可靠,而锁语句中不允许使用await语句的原因与某种原因有关。

Does anyone know why await isn't allowed within the body of a lock statement? 有谁知道为什么锁语句体内不允许等待?


#1楼

参考:https://stackoom.com/question/VwNu/为什么不能在lock语句的主体内使用-await-运算符


#2楼

This referes to http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx , http://winrtstoragehelper.codeplex.com/ , Windows 8 app store and .net 4.5 这referes到http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspxhttp://winrtstoragehelper.codeplex.com/ ,Windows 8应用商店和.NET 4.5

Here is my angle on this: 这是我的看法:

The async/await language feature makes many things fairly easy but it also introduces a scenario that was rarely encounter before it was so easy to use async calls: reentrance. 异步/等待语言功能使很多事情变得相当容易,但是它也引入了一种在使用异步调用如此容易之前很少遇到的场景:重入。

This is especially true for event handlers, because for many events you don't have any clue about whats happening after you return from the event handler. 对于事件处理程序尤其如此,因为对于许多事件,从事件处理程序返回后,您对发生的事情一无所知。 One thing that might actually happen is, that the async method you are awaiting in the first event handler, gets called from another event handler still on the same thread. 实际上可能发生的一件事是,您在第一个事件处理程序中等待的async方法是从仍在同一线程上的另一个事件处理程序调用的。

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. 这是我在Windows 8 App Store应用程序中遇到的一个真实场景:我的应用程序有两个框架:进入和离开一个框架我要加载/保护一些数据到文件/存储。 OnNavigatedTo/From events are used for the saving and loading. OnNavigatedTo / From事件用于保存和加载。 The saving and loading is done by some async utility function (like http://winrtstoragehelper.codeplex.com/ ). 保存和加载是通过一些异步实用程序功能(例如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. 从第1帧导航到第2帧或从其他方向导航时,将调用并等待异步加载和安全操作。 The event handlers become async returning void => they cant be awaited. 事件处理程序变为异步,返回void =>,无法等待它们。

However, the first file open operation (lets says: inside a save function) of the utility is async too and so the first await returns control to the framework, which sometime later calls the other utility (load) via the second event handler. 但是,该实用程序的第一个文件打开操作(在保存函数中说:)也是异步的,因此第一个await将控制权返回给框架,该控件稍后有时会通过第二个事件处理程序调用另一个实用程序(加载)。 The load now tries to open the same file and if the file is open by now for the save operation, fails with an ACCESSDENIED exception. 加载现在尝试打开同一文件,并且如果该文件现在已打开以进行保存操作,则加载失败,并出现ACCESSDENIED异常。

A minimum solution for me is to secure the file access via a using and an AsyncLock. 对我来说,最小的解决方案是通过使用和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);
    }
}

Please note that his lock basically locks down all file operation for the utility with just one lock, which is unnecessarily strong but works fine for my scenario. 请注意,他的锁基本上只用一个锁就可以锁定该实用程序的所有文件操作,这虽然不必要,但对我的情况来说效果很好。

Here is my test project: a windows 8 app store app with some test calls for the original version from http://winrtstoragehelper.codeplex.com/ and my modified version that uses the AsyncLock from Stephen Toub http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx . 是我的测试项目:Windows 8应用程序商店应用程序,其中一些测试调用来自http://winrtstoragehelper.codeplex.com/的原始版本,以及经过修改的版本,该版本使用来自Stephen Toub http://blogs.msdn的AsyncLock 。 com / b / pfxteam / archive / 2012/02/12 / 10266988.aspx

May I also suggest this link: http://www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx 我也可以建议以下链接: http : //www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx


#3楼

Hmm, looks ugly, seems to work. 嗯,看起来很丑,似乎可以工作。

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

#4楼

Use SemaphoreSlim.WaitAsync method. 使用SemaphoreSlim.WaitAsync方法。

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

#5楼

Stephen Taub has implemented a solution to this question, see Building Async Coordination Primitives, Part 7: AsyncReaderWriterLock . Stephen Taub实现了此问题的解决方案,请参阅构建异步协调基元,第7部分:AsyncReaderWriterLock

Stephen Taub is highly regarded in the industry, so anything he writes is likely to be solid. Stephen Taub在该行业中享有很高的声誉,因此他撰写的任何文章都可能很扎实。

I won't reproduce the code that he posted on his blog, but I will show how to use it: 我不会复制他在博客上发布的代码,但是我将展示如何使用它:

/// <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.
        }
    }
}

If you a method that's baked into the .NET framework, use SemaphoreSlim.WaitAsync instead. 如果您将方法嵌入到.NET框架中,请改用SemaphoreSlim.WaitAsync You won't get a reader/writer lock, but you will get tried and tested implementation. 您不会获得读取器/写入器锁,但是将获得经过实践检验的实现。


#6楼

I did try using a Monitor (code below) which appears to work but has a GOTCHA... when you have multiple threads it will give... System.Threading.SynchronizationLockException Object synchronization method was called from an unsynchronized block of code. 我确实尝试使用似乎可以工作但具有GOTCHA的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;
        }
    }
}

Prior to this I was simply doing this, but it was in an ASP.NET controller so it resulted in a deadlock. 在此之前,我只是这样做,但是它在ASP.NET控制器中,因此导致了死锁。

public async Task<FooResponse> ModifyFooAsync() { lock(lockObject) { return SomeFunctionToModifyFooAsync.Result; } }

猜你喜欢

转载自blog.csdn.net/p15097962069/article/details/107667456
今日推荐