如果程式用到了併發技術,那就要特別留意這種情況:一段代碼需要修改數據,同時其他代碼需要訪問同一個數據。 這種情況就需要考慮同步地訪問數據。 如果下麵三個條件都滿足,就必須用同步來保護共用的數據。 多段代碼正在併發運行; 這幾段代碼在訪問(讀或寫)同一個數據; 至少有一段代碼在修改(寫)數據。 一 阻 ...
如果程式用到了併發技術,那就要特別留意這種情況:一段代碼需要修改數據,同時其他代碼需要訪問同一個數據。
這種情況就需要考慮同步地訪問數據。
如果下麵三個條件都滿足,就必須用同步來保護共用的數據。
-
多段代碼正在併發運行;
-
這幾段代碼在訪問(讀或寫)同一個數據;
-
至少有一段代碼在修改(寫)數據。
一 阻塞鎖
如果有多個線程需要安全地讀寫共用數據,這種情況可以考慮使用lock語句。
一個線程進入鎖後,在鎖被釋放之前其他線程是無法進入的:
class MyClass { private readonly object _mutex = new object(); private int _value; public void Increment() { lock (_mutex) { _value = _value + 1; } } }
.NET 框 架 中 還 有 很 多 其 他 類 型 的 鎖, 如 Monitor、SpinLock、ReaderWriterLockSlim。
關於lock的使用,有四條重要的準則:
-
限制鎖的作用範圍:
要儘量限制鎖的作用範圍。應該把 lock 語句使用的對象設為私有成員,並且永遠不要暴露給非本類的方法。
每個類型通常最多只有一個鎖。如果一個類型有多個鎖,可考慮通過重構把它分拆成多個獨立的類型。
lock可以鎖定任何引用類型,但是建議為 lock 語句定義一個專用的成員,就像上例中那樣。
-
文檔中明確鎖保護的內容;
-
鎖範圍內的代碼儘量少:在鎖定時執行的代碼要儘可能得少。要特別小心阻塞調用。在鎖定時不要做任何阻塞操作。
-
在控制鎖的時候絕不運行隨意的代碼:
在鎖定時絕不要調用隨意的代碼。隨意的代碼包括引發事件、調用虛擬方法、調用委托。
如果一定要運行隨意的代碼,就在釋放鎖之後運行。
二 非同步鎖
如果多個代碼塊需要安全地讀寫共用數據,並且這些代碼塊可能使用 await 語句,可以考慮使用SemaphoreSlim。
class MyClass1 { private readonly SemaphoreSlim _mutex = new SemaphoreSlim(1); private int _value; public async Task DelayAndIncrementAsync() { await _mutex.WaitAsync(); try { var oldValue = _value; await Task.Delay(TimeSpan.FromSeconds(oldValue)); _value = oldValue + 1; } finally { _mutex.Release(); } } }
三 阻塞信號
如果需要從一個線程發送信號給另一個線程,可以考慮使用ManualResetEventSlim。
一個ManualResetEventSlim的對象處於這兩種狀態其中之一:標記的(signaled)或未標記的(unsignaled)。
每個線程都可以把事件設置為signaled 狀態,也可以把它重置為 unsignaled 狀態。線程也可等待事件變為 signaled 狀態。
下麵的兩個方法被兩個獨立的線程調用,一個線程等待另一個線程的信號:
class MyClass2 { private readonly ManualResetEventSlim _mres = new ManualResetEventSlim(); private int _value; public int WaitForInitialization() { _mres.Wait(); return _value; } public void InitializeFromAnotherThread() { _value = 13; _mres.Set(); } }
在 .NET 框架中,還有一些線程同步信號類型。ManualResetEvent、AutoResetEvent、CountdownEvent。
四 非同步信號
需要在代碼的各個部分間發送通知,並且要求接收方必須進行非同步等待。
如果該通知只需要發送一次,那可用 TaskCompletionSource<T> 非同步發送。
發送代碼調用TrySetResult,接收代碼等待它的 Task 屬性:
class MyClass3 { private readonly TaskCompletionSource<object> _tcs = new TaskCompletionSource<object>(); private int _value1; private int _value2; public async Task<int> WaitForInitializationAsync() { await _tcs.Task; return _value1 + _value2; } public void Initialize() { _value1 = 13; _value2 = 17; _tcs.TrySetResult(null); } }
以上。