執行緒同步處理 (C# 和 Visual Basic)

下列章節描述的功能和類別,可用來同步處理在多執行緒應用程式中資源的存取。

在應用程式中使用多執行緒的其中一個優點,就是每個執行緒會同步執行。 對於 Windows 應用程式而言,這能讓耗費時間的工作在幕後執行,而應用程式視窗和控制項仍能保持回應能力。 針對伺服器應用程式,多執行緒處理則提供了一種能力,可用不同的執行緒處理每個接收到的請求。 否則,在先前的請求完全得到滿足之前,每個新的請求都不會獲得服務。

不過,執行緒的非同步性質表示必須協調對資源 (例如,檔案控制代碼、網路連線和記憶體) 的存取。 否則兩個或以上的執行緒可能會同時存取相同資源,而每個執行緒都不知道其他執行緒的行動。 結果是發生無法預期的資料毀損。

針對在整數數字資料型別上的簡單操作,同步處理執行緒可使用 Interlocked 類別的成員來完成。 至於所有其他的資料型別和非執行緒安全的資源,只能使用本主題中的建構來安全執行多執行緒處理。

如需多執行緒應用程式的背景資訊,請參閱:

鎖定和 SyncLock 關鍵字

lock (C#) 和 SyncLock (Visual Basic) 陳述式可用來確保程式碼區塊會完成執行,而不會被其他執行緒所干擾。 為指定物件取得程式碼區塊期間的互斥鎖定,便能完成這一點。

lock 或 SyncLock 陳述式會被給予物件做為引數,並會後接只能由一個執行緒在同時執行的程式碼區塊。 例如:

Public Class TestThreading
    Dim lockThis As New Object

    Public Sub Process()
        SyncLock lockThis
            ' Access thread-sensitive resources.
        End SyncLock
    End Sub
End Class
public class TestThreading
{
    private System.Object lockThis = new System.Object();

    public void Process()
    {

        lock (lockThis)
        {
            // Access thread-sensitive resources.
        }
    }

}

提供給 lock 關鍵字的引數必須是依據參考型別的物件,且用來定義鎖定的範圍。 在上述範例中,鎖定範圍是限制為此函式,因為在函式外部不存在物件 lockThis 的參考。 如果這類參考並不存在,鎖定範圍便會擴充至該物件。 嚴格說來,提供的物件僅用來唯一識別正由多執行緒共用的資源,所以可能是任意的類別執行個體。 不過實際上,此物件通常代表執行緒同步處理所需要的資源。 例如,如果容器物件是由多執行緒使用,則該容器可以傳遞至鎖定,在鎖定之後的同步化程式碼區塊將可存取容器。 只要其他執行緒在存取容器前鎖定了同一個容器,就可以安全地同步處理對物件的存取。

一般而言,您最好避免鎖定 public 型別,或避免鎖定不在應用程式控制範圍內的物件執行個體。 例如,若執行個體可以公開地被存取,lock(this) 可能會產生問題,因為在您控制範圍之外的程式碼可能也會鎖定物件。 這樣可能會建立死結狀況,在此狀況下有兩個或以上的執行緒等候同一個物件的釋放。 相對於物件,鎖定公用資料型別會造成相同原因的問題。 而鎖定常值字串則特別具風險性,因為常值字串是由 Common Language Runtime (CLR) 所「拘留」(interned)。 這表示對於整個程式,任何指定之字串常值有一個執行個體,完全相同的物件代表在所有執行緒上、所有執行的應用程式定義域中的常值。 因此,不論在應用程式處理序的任何地方,置於相同內容字串上的鎖定將會鎖定應用程式中該字串的所有執行個體。 因此,最好鎖定未被拘留的 private 或 protected 成員。 某些類別特別為鎖定提供成員。 例如,Array 型別提供了 SyncRoot。 許多集合型別也提供 SyncRoot 成員。

如需 lock 和 SyncLock 陳述式的詳細資訊,請參閱下列主題:

監視器

就像 lock 和 SyncLock 關鍵字一樣,監視器會避免多執行緒同時執行程式碼區塊。 Enter 方法能讓一個而且是唯一的執行緒處理下列陳述式;其他執行緒會被阻斷,直到執行的執行緒呼叫 Exit 為止。 就好像使用 lock 關鍵字一樣。 例如:

SyncLock x
    DoSomething()
End SyncLock
lock (x)
{
    DoSomething();
}

這種做法相當於:

Dim obj As Object = CType(x, Object)
System.Threading.Monitor.Enter(obj)
Try
    DoSomething()
Finally
    System.Threading.Monitor.Exit(obj)
End Try
System.Object obj = (System.Object)x;
System.Threading.Monitor.Enter(obj);
try
{
    DoSomething();
}
finally
{
    System.Threading.Monitor.Exit(obj);
}

使用 lock (C#) 關鍵字或 SyncLock (Visual Basic) 的偏好,一般要更勝於直接使用 Monitor 類別,這是因為 lock 或 SyncLock 較為簡潔,而且因為 lock 或 SyncLock 可確保即使受保護的程式碼擲回例外狀況,也會釋放基礎監視器。 可以使用 finally 關鍵字來完成這一點,不論是否擲回例外狀況,此關鍵字都會執行其關聯的程式碼區塊。

同步處理事件和等候控制代碼

使用鎖定或監視器避免執行緒敏感的程式碼區塊同時執行,會非常有用,但這些建構並不允許執行緒和另一個執行緒溝通事件。 這需要「同步處理事件」(Synchronization Event),此事件是具有兩種狀態 (發出信號和未發出信號) 其中一種的物件,這些狀態可用來啟動和暫止執行緒。 讓執行緒等待未發出信號的同步處理事件會暫止執行緒,將事件狀態變更為發出信號則可以啟動執行緒。 如果執行緒嘗試等待已經發出訊號的事件,執行緒便會繼續執行,不會受到延遲。

同步處理事件有兩種,分別是 AutoResetEventManualResetEvent。 它們唯一的不同之處,是在於 AutoResetEvent 會在啟動執行緒後的任何時間,自動從發出信號變更至未發出信號。 相反地,ManualResetEvent 則允許其發出信號的狀態啟動任何數目的執行緒,並只有在呼叫 Reset 方法時才還原成未發出信號的狀態。

藉由呼叫 WaitOneWaitAnyWaitAll 其中一種等候方法,可以讓執行緒等候事件發生。 WaitHandle.WaitOne() 會導致執行緒等候,直到單一事件發出信號為止;WaitHandle.WaitAny() 則會阻斷執行緒,直到一個或多個指定的事件變成發出信號為止;而 WaitHandle.WaitAll() 會阻斷執行緒,直到所有指定的事件都成為發出信號狀態為止。 當呼叫事件的 Set 方法時,該事件就會變成發出信號。

在下列範例中,會由 Main 函式建立和啟動執行緒。 新的執行緒使用 WaitOne 方法等候事件。 執行緒會被暫止,直到執行 Main 函式的主要執行緒對事件發出信號為止。 一旦事件變成發出信號,就會傳回輔助執行緒。 在這種情況下,因為事件只用來進行一個執行緒的啟動,所以不能使用 AutoResetEventManualResetEvent 類別。

Imports System.Threading

Module Module1
    Dim autoEvent As AutoResetEvent

    Sub DoWork()
        Console.WriteLine("   worker thread started, now waiting on event...")
        autoEvent.WaitOne()
        Console.WriteLine("   worker thread reactivated, now exiting...")
    End Sub

    Sub Main()
        autoEvent = New AutoResetEvent(False)

        Console.WriteLine("main thread starting worker thread...")
        Dim t As New Thread(AddressOf DoWork)
        t.Start()

        Console.WriteLine("main thread sleeping for 1 second...")
        Thread.Sleep(1000)

        Console.WriteLine("main thread signaling worker thread...")
        autoEvent.Set()
    End Sub
End Module
using System;
using System.Threading;

class ThreadingExample
{
    static AutoResetEvent autoEvent;

    static void DoWork()
    {
        Console.WriteLine("   worker thread started, now waiting on event...");
        autoEvent.WaitOne();
        Console.WriteLine("   worker thread reactivated, now exiting...");
    }

    static void Main()
    {
        autoEvent = new AutoResetEvent(false);

        Console.WriteLine("main thread starting worker thread...");
        Thread t = new Thread(DoWork);
        t.Start();

        Console.WriteLine("main thread sleeping for 1 second...");
        Thread.Sleep(1000);

        Console.WriteLine("main thread signaling worker thread...");
        autoEvent.Set();
    }
}

Mutex 物件

"Mutex" 類似於監視器;避免一次有一個以上的執行緒同時執行程式碼區塊。 事實上,"Mutex" 的名稱就是「互斥」(Mutually Exclusive) 的縮短形式。 然而不同於監視器,Mutex 可以用來跨處理序同步處理執行緒。 Mutex 是由 Mutex 類別所表示。

當使用做為處理序間的同步處理時,Mutex 稱為「具名的 Mutex」(Named Mutex),因為它有在另一個應用程式中使用,所以無法由全域或靜態變數共用。 必須為它指定名稱,如此兩個應用程式才能存取同一個 mutex 物件。

雖然 mutex 可用做處理序間的執行緒同步處理,但通常還是偏好使用 Monitor,因為監視器是特別為 .NET Framework 設計,所以能對資源有更好的利用。 相反地,Mutex 類別是 Win32 建構的包裝函式。 Mutex 比監視器還要強大,但它在計算上所需的 Interop 轉換,也比 Monitor 類別所需的轉換耗費更多計算資源。 如需使用 mutex 的範例,請參閱 Mutex

Interlocked 類別

您可以使用 Interlocked 類別的方法,防止在多執行緒嘗試同時更新或比較相同值時發生問題。 這個類別的方法讓您能夠安全的遞增、遞減、交換和比較任何執行緒的值。

ReaderWriter 鎖定

在某些狀況下,您可能只要在寫入資料時鎖定資源,並允許多個用戶端在資料沒有更新時同時讀取資料。 ReaderWriterLock 類別會在執行緒修改資源時強制獨佔存取資源,但在讀取資源時則允許非獨佔存取。 ReaderWriter 鎖定是獨佔鎖定的替代方法,這個方法非常有用,因為獨佔鎖定會讓其他的執行緒等待,即使這些執行緒不需要更新資料。

死結

執行緒同步在多執行緒應用程式 (Multithreaded Application) 中非常重要,但建立 deadlock 一向很危險,因為其中會有多個執行緒互相等候彼此,因而使應用程式中止。 死結的情況類似車子停靠在有四個方向的停車站,而且每個人都要等候另一個人才能出發。 避免發生死結是很重要的,關鍵則在於要小心規劃。 在開始寫程式前先圖解多執行緒應用程式,通常都可以預測到死結狀況。

相關章節

HOW TO:使用執行緒集區 (C# 和 Visual Basic)

如何使用 Visual C# .NET 或 Visual C# 2005 在多執行緒環境中同步化共用資源的存取

HOW TO:使用 Visual C# .NET 建立執行緒

如何使用 Visual C# 來提交至執行緒集區的工作項目

如何使用 Visual C# .NET 或 Visual C# 2005 在多執行緒環境中同步化共用資源的存取

請參閱

參考

SyncLock 陳述式

lock 陳述式 (C# 參考)

Thread

WaitOne

WaitAny

WaitAll

Join

Start

Sleep

Monitor

Mutex

AutoResetEvent

ManualResetEvent

Interlocked

WaitHandle

EventWaitHandle

System.Threading

Set

概念

多執行緒應用程式 (C# 和 Visual Basic)

Mutex

監視器

Interlocked 作業

AutoResetEvent

同步處理多執行緒處理的資料

其他資源

實作 CLR 非同步程式撰寫模型

使用 C# 簡化 APM

死結監視器

元件中的多執行緒

HOW TO: 使用 Visual C# .NET 同步處理對多執行緒環境中共用資源的存取