使用await时Monitor.Exit上的SynchronizationLockException

vto*_*ola 8 c# multithreading synchronization task-parallel-library async-await

我正在创建一段代码,从我们拥有的遗留系统中获取网页.为了避免过多的查询,我正在缓存获取的URL.我正在使用Monitor.Enter,Monitor.Exit并仔细检查以避免该请求被发出两次,但在释放锁定时Monitor.Exit,我得到此异常:

System.Threading.SynchronizationLockException was caught
  HResult=-2146233064
  Message=Object synchronization method was called from an unsynchronized block of code.
  Source=MyApp
  StackTrace:
       at MyApp.Data.ExProvider.<OpenFeature>d__0.MoveNext() in c:\Users\me\Documents\Visual Studio 2013\Projects\MyApp\MyApp\Data\ExProvider.cs:line 56
    --- End of stack trace from previous location where exception was thrown ---
       at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
       at MyApp.Data.ExProvider.<GetSupportFor>d__15.MoveNext() in c:\Users\me\Documents\Visual Studio 2013\Projects\MyApp\MyApp\Data\ExProvider.cs:line 71
  InnerException: 
Run Code Online (Sandbox Code Playgroud)

线56是Monitor.Exit.这是执行操作的代码:

private async Task<Stream> OpenReport(String report)
{
    var file = _directory.GetFiles(report+ ".html");
    if (file != null && file.Any())
        return file[0].OpenRead();
    else
    {
        try
        {
            Monitor.Enter(_locker);
            FileInfo newFile = new FileInfo(Path.Combine(_directory.FullName, report + ".html"));
            if (!newFile.Exists) // Double check
            {
                using (var target = newFile.OpenWrite())
                {
                    WebRequest request = WebRequest.Create(BuildUrl(report));
                    var response = await request.GetResponseAsync();
                    using (var source = response.GetResponseStream())
                        source.CopyTo(target);
                }
            }
            return newFile.OpenRead();
        }
        finally
        {
            Monitor.Exit(_locker);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

那么,什么是有问题的awaitMonitor?是因为它与什么时候Monitor.Enter不是同一个线程Monitor.Exit

i3a*_*non 30

你不能await一个内部的工作lock范围(这句法糖Monitor.EnterMonitor.Exit).Monitor直接使用将欺骗编译器而不是框架.

async-await没有像线程一样的线程关联Monitor.之后,该代码await会在不同的线程比以前的代码可能运行.这意味着释放它的线程Monitor不一定是获得它的线程.

要么async-await在这种情况下不使用,要么使用不同的同步构造,SemaphoreSlim或者AsyncLock您可以自己构建.这是我的:https://stackoverflow.com/a/21011273/885318

  • 它可以,但是将SemaphoreSlim设置为1会更容易,因为你可以等待它并且它不是线程仿射的 (2认同)

Ste*_*ary 9

然而,在SendRequest中,我需要'等待',因此我无法使用锁,因为我没有多想,所以同步的解决方案是使用Monitor.

应该多考虑一下.:)

使用带async代码的阻塞锁有两个问题.

第一个问题是 - 在一般情况下 - async方法可以在不同的线程上继续执行.大多数阻塞锁是线程仿射的,这意味着它们必须从拥有它们的线程(获取锁的同一线程)中释放.这是违反Monitor线程亲和力导致的SynchronizationLockException.如果await捕获执行上下文(例如,UI上下文)并且使用它来恢复该async方法(例如,在UI线程上),则不会发生该问题.或者,如果你很幸运,并且该async方法恰好在同一线程池线程上恢复.

但是,即使您避免了第一个问题,您仍然会遇到第二个问题:当某个async方法在某个await点"暂停"时,任何代码都可以执行.这违反了基本的锁定规则("在持有锁时不执行任意代码").例如,线程仿射锁(包括Monitor)通常是可重入的,因此即使在UI线程场景中,当您的async方法被"暂停"(并持有锁)时,在UI线程上运行的其他方法也可以在没有锁定的情况下进行锁定.任何问题.

在Windows Phone 8上,请SemaphoreSlim改用.这是一种允许阻塞和异步协调的类型.使用Wait了阻断锁和WaitAsync一个异步锁.