C#讀寫者線程(用AutoResetEvent實現同步)
1. AutoResetEvent簡介
通知正在等待的線程已發生事件。無法繼承此類。
常用方法簡介:
AutoResetEvent(bool initialState):構造函數,用一個指示是否將初始狀態設置為終止的布爾值初始化該類的新實例。
false:無信號,子線程的WaitOne方法不會被自動調用
true:有信號,子線程的WaitOne方法會被自動調用
public bool Reset ():將事件狀態設置為非終止狀態,導致線程阻止;如果該操作成功,則返回true;否則,返回false。
public bool Set ():將事件狀態設置為終止狀態,允許一個或多個等待線程繼續;如果該操作成功,則返回true;否則,返回false。
對於具有 EventResetMode.AutoReset(包括 AutoResetEvent)的 EventWaitHandle,Set 方法釋放單個線程。如果沒有等待線程,等待句柄將一直保持終止狀態,直到某個線程嘗試等待它,或者直到它的 Reset 方法被調用。
對於具有 EventResetMode.ManualReset(包括 ManualResetEvent)的 EventWaitHandle,調用Set 方法將使等待句柄一直保持終止狀態,直到它的 Reset 方法被調用。
WaitOne方法
當在派生類中重寫時,阻止當前線程,直到當前的 WaitHandle 收到信號。
WaitHandle.WaitOne () 當在派生類中重寫時,阻止當前線程,直到當前的 WaitHandle 收到信號。 由.NET Compact Framework 支持。
WaitHandle.WaitOne(Int32, Boolean) 在派生類中被重寫時,阻止當前線程,直到當前的WaitHandle 收到信號,使用 32 位有符號整數度量時間間隔並指定是否在等待之前退出同步域。由 .NET Compact Framework 支持。
WaitHandle.WaitOne(TimeSpan, Boolean) 在派生類中被重寫時,阻止當前線程,直到當前實例收到信號,使用 TimeSpan 度量時間間隔並指定是否在等待之前退出同步域。
2. 讀寫者線程例子
本例子中,主線程作為寫線程,要對某個數據(本例中是個變量)賦值(即寫動作),而讀線程則等待寫線程每次寫完數據發出通知,待讀線程收到通知后,將數據讀出並顯示。
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace TestAutoResetEvent
{
///
/// 讀寫者線程
/// 主線程寫,子線程讀,且只有將數據寫入后,讀線程才能將其讀出
///
class Program
{
//寫線程將數據寫入myData
static int myData = 100;
//讀寫次數
const int readWriteCount = 10;
//false:初始時沒有信號
static AutoResetEvent autoResetEvent = new AutoResetEvent(false);
static void Main(string[] args)
{
//開啟一個讀線程(子線程)
Thread readerThread = new Thread(new ThreadStart(ReadThreadProc));
readerThread.Name = "ReaderThread";
readerThread.Start();
for (int i = 1; i <= readWriteCount; i++)
{
Console.WriteLine("MainThread writing : {0}", i);
//主(寫)線程將數據寫入
myData = i;
//主(寫)線程發信號,說明值已寫過了
//即通知正在等待的線程有事件發生
autoResetEvent.Set();
Thread.Sleep(0);
}
//終止線程
readerThread.Abort();
}
static void ReadThreadProc()
{
while (true)
{
//在數據被寫入前,讀線程等待(實際上是等待寫線程發出數據寫完的信號)
autoResetEvent.WaitOne();
Console.WriteLine("{0} reading : {1}", Thread.CurrentThread.Name, myData);
}
}
}
}運行結果如下:
由運行結果可以看出,寫線程寫入的數據有丟失,主要原因是寫線程沒有給讀線程留足夠的時間去進行讀操作。
3. 對1進行修改
將主線程睡眠時間改為非0值,觀察運行結果。
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace TestAutoResetEvent
{
///
/// 讀寫者線程
/// 主線程寫,子線程讀,且只有將數據寫入后,讀線程才能將其讀出
///
class Program
{
//寫線程將數據寫入myData
static int myData = 100;
//讀寫次數
const int readWriteCount = 10;
//false:初始時沒有信號
static AutoResetEvent autoResetEvent = new AutoResetEvent(false);
static void Main(string[] args)
{
//開啟一個讀線程(子線程)
Thread readerThread = new Thread(new ThreadStart(ReadThreadProc));
readerThread.Name = "ReaderThread";
readerThread.Start();
for (int i = 1; i <= readWriteCount; i++)
{
Console.WriteLine("MainThread writing : {0}", i);
//主(寫)線程將數據寫入
myData = i;
//主(寫)線程發信號,說明值已寫過了
//即通知正在等待的線程有事件發生
autoResetEvent.Set();
Thread.Sleep(1);
}
//終止線程
readerThread.Abort();
}
static void ReadThreadProc()
{
while (true)
{
//在數據被寫入前,讀線程等待(實際上是等待寫線程發出數據寫完的信號)
autoResetEvent.WaitOne();
Console.WriteLine("{0} reading : {1}", Thread.CurrentThread.Name, myData);
}
}
}
}運行結果如下:
有結果可知,當主線程睡眠時間大於0值時,讀線程即有足夠的時間讀取寫線程寫入的數據。這個睡眠時間的長短可以根據實際應用中子線程的計算量設定。
4. 對1再進行修改
主線程在寫完數據后根本不睡嗎呢?這個時候會發生什么事情?
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace TestAutoResetEvent
{
///
/// 讀寫者線程
/// 主線程寫,子線程讀,且只有將數據寫入后,讀線程才能將其讀出
///
class Program
{
//寫線程將數據寫入myData
static int myData = 100;
//讀寫次數
const int readWriteCount = 10;
//false:初始時沒有信號
static AutoResetEvent autoResetEvent = new AutoResetEvent(false);
static void Main(string[] args)
{
//開啟一個讀線程(子線程)
Thread readerThread = new Thread(new ThreadStart(ReadThreadProc));
readerThread.Name = "ReaderThread";
readerThread.Start();
for (int i = 1; i <= readWriteCount; i++)
{
Console.WriteLine("MainThread writing : {0}", i);
//主(寫)線程將數據寫入
myData = i;
//主(寫)線程發信號,說明值已寫過了
//即通知正在等待的線程有事件發生
autoResetEvent.Set();
//Thread.Sleep(1);
}
//終止線程
readerThread.Abort();
}
static void ReadThreadProc()
{
while (true)
{
//在數據被寫入前,讀線程等待(實際上是等待寫線程發出數據寫完的信號)
autoResetEvent.WaitOne();
Console.WriteLine("{0} reading : {1}", Thread.CurrentThread.Name, myData);
}
}
}
}
運行結果如下:
有結果可知,不睡眠的情況和睡眠時間為0(即Thread.Sleep(0);)效果產不多,只是不睡眠丟失的數據更多了。
5. 對1再修改
將傳遞給AutoResetEvent的構造函數的參數設置為true,觀察運行結果。
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace TestAutoResetEvent
{
///
/// 讀寫者線程
/// 主線程寫,子線程讀,且只有將數據寫入后,讀線程才能將其讀出
///
class Program
{
//寫線程將數據寫入myData
static int myData = 100;
//讀寫次數
const int readWriteCount = 10;
//false:初始時沒有信號
static AutoResetEvent autoResetEvent = new AutoResetEvent(true);
static void Main(string[] args)
{
//開啟一個讀線程(子線程)
Thread readerThread = new Thread(new ThreadStart(ReadThreadProc));
readerThread.Name = "ReaderThread";
readerThread.Start();
for (int i = 1; i <= readWriteCount; i++)
{
Console.WriteLine("MainThread writing : {0}", i);
//主(寫)線程將數據寫入
myData = i;
//主(寫)線程發信號,說明值已寫過了
//即通知正在等待的線程有事件發生
autoResetEvent.Set();
Thread.Sleep(0);
}
//終止線程
readerThread.Abort();
}
static void ReadThreadProc()
{
while (true)
{
//在數據被寫入前,讀線程等待(實際上是等待寫線程發出數據寫完的信號)
autoResetEvent.WaitOne();
Console.WriteLine("{0} reading : {1}", Thread.CurrentThread.Name, myData);
}
}
}
}
運行結果如下:
若將主線程的睡眠時間改為任意非0值,其運行結果均為下圖所示的結果。
6. 其他修改
將主線程調用AutoResetEvent對象的Set方法刪除,分別對AutoResetEvent的構造函數的參數為false和true觀察運行結果。
為false,運行結果如下圖所示。
為true,運行結果如下圖所示。
至此,我想我們應該明白AutoResetEvent構造函數的參數的意義了。
false:無信號,子線程的WaitOne方法不會被自動調用;
true:有信號,子線程的WaitOne方法會被自動調用。