C# 监视器上的SynchronizationLockException。使用Wait时退出
我正在创建一段代码,从我们已有的遗留系统中获取一个网页。为了避免过度查询,我正在缓存获取的URL。我正在使用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
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(){}
@RasikBihariTiwariAsyncFunctionWithLock2
实际上不是异步的。它不等待任何东西,因此它在同一线程上同步继续。
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;
}
}
}