5

我正在創建一段從舊版系統獲取網頁的代碼。爲了避免過多的查詢,我緩存了獲取的URL。我使用Monitor.EnterMonitor.Exit和雙重檢查,以避免請求發出了兩次,但Monitor.Exit解除鎖定的時候,我得到這個異常:使用await時Monitor.Exit上的SynchronizationLockException

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.EnterMonitor.Exit的線程不一樣嗎?

回答

19

您不能awaitlock範圍內的任務(這是Monitor.EnterMonitor.Exit的語法糖)。直接使用Monitor會欺騙編譯器,但不會欺騙框架。

async-await沒有像Monitor那樣的線程親和性。 await之後的代碼可能會運行在與之前的代碼不同的線程中。這意味着釋放Monitor的線程不一定是獲得它的線程。

要麼在這種情況下不要使用async-await,要麼使用不同的同步構造,如SemaphoreSlimAsyncLock您可以自己構建。這裏是我的:https://stackoverflow.com/a/21011273/885318

+0

它應該與一個ManualResetEventSlim? – vtortola

+2

它可以,但是將SemaphoreSlim設置爲1會容易得多,因爲您可以等待它,並且它不是線程仿製的 – i3arnon

+0

感謝您的解釋。這很有道理。 – vtortola

6

然而,在SendRequest中,我需要「等待」,因此我無法使用鎖,因爲某些原因,我沒有多加考慮,所以同步的解決方案是使用Monitor。

應該給它更多的想法。 :)

使用async代碼的鎖定有兩個問題。

第一個問題是 - 在一般情況下 - async方法可能會在不同的線程上繼續執行。大多數阻塞鎖都是線程仿射的,這意味着它們必須從它們所擁有的10(與獲得鎖的線程相同)的線程中釋放。這是Monitor線程相似性的違反導致SynchronizationLockException。如果await捕獲執行上下文(例如,UI上下文)並且使用它來恢復方法(例如,在UI線程上),則不會發生此問題。或者如果你只是很幸運,並且async方法發生在同一線程池線程上恢復。

但是,即使您避免了第一個問題,仍然存在第二個問題:當async方法在await點處「暫停」時,任何代碼都可以執行。這違反了基本的鎖定規則(「在執行鎖定時不要執行任意代碼」)。例如,線程仿射鎖(包括Monitor)通常是可重入的,所以即使在UI線程場景中,當您的async方法處於「暫停」狀態(並持有鎖)時,UI線程上運行的其他方法也可以鎖定沒有任何問題。在Windows Phone 8上,改用SemaphoreSlim。這是一種允許阻塞和異步協調的類型。對於鎖定鎖使用Wait,對於異步鎖使用WaitAsync

+0

謝謝。 SemaphoreSlim看起來像我所需要的。 – warwolf

+0

供參考:答案合併來自http://stackoverflow.com/questions/21368917/c-sharp-synchronizationlockexception-on-monitor-exit-when-async-method-called-fr – Shog9

相關問題