使用await时Monitor.Exit上的SynchronizationLockException
我正在创建一段代码,从我们拥有的遗留系统中获取网页。 为了避免过多的查询,我正在缓存获得的URL。 我正在使用Monitor.Enter
, Monitor.Exit
和双重检查以避免该请求被发出两次,但是当使用Monitor.Exit
释放锁时,我收到此exception:
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.d__0.MoveNext() in c:UsersmeDocumentsVisual Studio 2013ProjectsMyAppMyAppDataExProvider.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.d__15.MoveNext() in c:UsersmeDocumentsVisual Studio 2013ProjectsMyAppMyAppDataExProvider.cs:line 71 InnerException:
第56行是Monitor.Exit
。 这是执行操作的代码:
private async Task 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); } } }
那么await
和Monitor
什么问题呢? 是因为它与Monitor.Enter
时的Monitor.Enter
不是同一个线程吗?
您无法await
lock
范围内的任务(这是Monitor.Enter
和Monitor.Exit
语法糖)。 直接使用Monitor
会欺骗编译器而不是框架。
async-await
没有像Monitor
那样的线程关联。 await
之后的代码可能会在与之前的代码不同的线程中运行。 这意味着释放Monitor
的线程不一定是获取它的线程。
在这种情况下,要么不使用async-await
,要么使用不同的同步构造,如SemaphoreSlim
或您可以自己构建的AsyncLock
。 这是我的: https : //stackoverflow.com/a/21011273/885318
然而,在SendRequest中,我需要’等待’,因此我无法使用锁,因为我没有多想,所以同步的解决方案是使用Monitor。
应该多考虑一下。 ?
使用带有async
代码的阻塞锁有两个问题。
第一个问题是 – 在一般情况下 – async
方法可能会继续在不同的线程上执行。 大多数阻塞锁是线程仿射的,这意味着它们必须从拥有它们的线程(获取锁的同一线程)中释放。 违反Monitor
thread-affinity会导致SynchronizationLockException
。 如果await
捕获执行上下文(例如,UI上下文)并且使用它来恢复async
方法(例如,在UI线程上),则不会发生此问题。 或者,如果你很幸运, async
方法恰好在同一线程池线程上恢复。
但是,即使您避免了第一个问题,您仍然会遇到第二个问题:任何任意代码都可以执行,而async
方法在await
点处“暂停”。 这违反了基本的锁定规则(“在持有锁时不执行任意代码”)。 例如,线程仿射锁(包括Monitor
)通常是可重入的,因此即使在UI线程场景中,当您的async
方法被“暂停”(并保持锁定)时,在UI线程上运行的其他方法也可以采用锁没有任何问题。
在Windows Phone 8上,请改用SemaphoreSlim
。 这是一种允许阻塞和异步协调的类型。 使用Wait
阻塞锁和WaitAsync
进行异步锁定。
您可以使用interlocked类来模拟lock语句,这里是代码:
上述就是C#学习教程:使用await时Monitor.Exit上的SynchronizationLockException分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!
private async Task 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; } } }
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/1009982.html