使用await时Monitor.Exit上的SynchronizationLockException

时间:2014-01-28 11:17:18

标签: c# multithreading synchronization task-parallel-library async-await

我正在创建一段代码,从我们拥有的遗留系统中获取网页。为了避免过多的查询,我正在缓存获得的URL。我正在使用Monitor.EnterMonitor.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: 

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

那么awaitMonitor会出现什么问题?是因为Monitor.Enter时的线程与Monitor.Exit时的线程不同?

3 个答案:

答案 0 :(得分:28)

您无法await lock范围内的任务(Monitor.EnterMonitor.Exit的语法糖)。直接使用Monitor会欺骗编译器,但不会欺骗框架。

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

在这种情况下,请勿使用async-await,要么使用其他同步构造,例如SemaphoreSlimAsyncLock,您可以自行构建。这是我的:https://stackoverflow.com/a/21011273/885318

答案 1 :(得分:8)

  

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

应该多考虑一下。 :)

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

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

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

在Windows Phone 8上,使用SemaphoreSlim代替。这是一种允许阻塞和异步协调的类型。使用Wait进行阻止锁定,使用WaitAsync进行异步锁定。

答案 2 :(得分:0)

您可以使用interlocked类来模拟lock语句,这里是代码:

    private async Task<Stream> OpenReport(String report)
    {
        var file = _directory.GetFiles(report + ".html");
        if (file != null && file.Any())
            return file[0].OpenRead();
        else
        {
            object locker = _locker;
            try
            {
                while (locker == null || Interlocked.CompareExchange(ref _locker, null, locker) != locker)
                {
                    await Task.Delay(1);
                    locker = _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
            {
                _locker = locker;
            }
        }
    }