C# 监视器上的SynchronizationLockException。使用Wait时退出

C# 监视器上的SynchronizationLockException。使用Wait时退出,c#,multithreading,synchronization,task-parallel-library,async-await,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

我正在创建一段代码,从我们已有的遗留系统中获取一个网页。为了避免过度查询,我正在缓存获取的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: 
那么
wait
Monitor
有什么问题呢?是不是因为当
监视时,输入的线程与当
监视时退出的线程不同

然而,在SendRequest中,我需要“等待”,因此我无法使用lock,因为我没有考虑太多的原因,所以同步的解决方案是使用Monitor

应该多考虑一下

async
code使用阻塞锁有两个问题

第一个问题是,在一般情况下,
async
方法可能会在不同的线程上继续执行。大多数阻塞锁是线程仿射的,这意味着它们必须从拥有它们的线程(获取锁的同一线程)中释放。正是这种违反
监视器
线程相关性的行为导致了
同步锁异常
。如果
await
捕获执行上下文(例如,UI上下文)并用于恢复
async
方法(例如,在UI线程上),则不会发生此问题。或者,如果您运气好,
async
方法恰好在同一个线程池线程上恢复

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


在Windows Phone 8上,改用
信号量LIM
。这是一种允许阻塞和异步协调的类型。使用
Wait
进行阻塞锁定,使用
WaitAsync
进行异步锁定。

您不能
等待
锁定范围内的任务(这是
监视器的语法糖。输入
监视器。退出
)。直接使用
监视器将愚弄编译器,但不会愚弄框架

async await
不像
监视器那样具有线程关联性。
wait
之后的代码可能会在与其之前的代码不同的线程中运行。这意味着释放
监视器的线程不一定是获取它的线程


在这种情况下,不要使用
async wait
,或者使用不同的同步构造,如
SemaphoreSlim
AsyncLock
,您可以自己构建。这是我的:

您可以使用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;
            }
        }
    }
专用异步任务OpenReport(字符串报告)
{
var file=_directory.GetFiles(report+“.html”);
if(file!=null&&file.Any())
返回文件[0]。OpenRead();
其他的
{
对象锁定器=_锁定器;
尝试
{
while(locker==null | |联锁。比较交换(ref _locker,null,locker)!=locker)
{
等待任务。延迟(1);
储物柜=_储物柜;
}
FileInfo newFile=newfileinfo(Path.Combine(_directory.FullName,report+“.html”);
如果(!newFile.Exists)//请仔细检查
{
使用(var target=newFile.OpenWrite())
{
WebRequest=WebRequest.Create(BuildUrl(report));
var response=wait request.GetResponseAsync();
使用(var source=response.GetResponseStream())
source.CopyTo(目标);
}
}
返回newFile.OpenRead();
}
最后
{
_储物柜=储物柜;
}
}
}

谢谢。SemaphoreSlim看起来像我需要的。仅供参考:答案合并自ManualResetEventSlim是否可以?可以,但如果将SemaphoreSlim设置为1,则更容易,因为您可以等待它,并且不需要解释。这很有意义。当我期望SynchronizationLockException时,我在下面的代码块中没有得到任何异常。不确定为什么它没有按照已知的等待和锁定行为引发异常。
static void Main(string[]args){AsyncFunctionWithLock();}私有静态异步void AsyncFunctionWithLock(){Monitor.Enter(lockObject);{await AsyncFunctionWithLock2();}Monitor.Exit(lockObject);}私有静态异步任务AsyncFunctionWithLock2(){}
@RasikBihariTiwari
AsyncFunctionWithLock2
实际上不是异步的。它不等待任何东西,因此它在同一线程上同步继续。
    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;
            }
        }
    }