摘要:[.NET] Thread Separate Event
撰寫多執行緒物件時,執行速度是一項很重要的設計考量。通常為了加快執行速度,會將執行運作邏輯的主執行緒,跟發出事件的事件執行緒做隔離設計。這樣的隔離設計可以不讓主執行緒,因為外部事件處理而停頓。而這樣的設計下,為了簡化執行緒管理,可以採用ThreadPool來完成事件執行緒的工作。簡單的程式範例如下:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var obj = new ClassA();
obj.NotifyArrived += new Action<int>(obj_NotifyArrived);
obj.Start();
Console.ReadLine();
obj.Stop();
}
static void obj_NotifyArrived(int data)
{
Console.WriteLine(data);
}
}
public class ClassA
{
// Fields
private bool _isRunning = true;
// Methods
public void Start()
{
Thread thread = new Thread(this.Run);
thread.Start();
}
public void Stop()
{
_isRunning = false;
}
private void Run()
{
int i = 0;
while (_isRunning == true)
{
Thread.Sleep(100);
this.OnNotifyArrived(i++);
}
}
// Events
public event System.Action<int> NotifyArrived;
private void OnNotifyArrived(int data)
{
WaitCallback handlerDelegate = delegate(object state)
{
var handler = this.NotifyArrived;
if (handler != null)
{
handler(data);
}
};
ThreadPool.QueueUserWorkItem(handlerDelegate);
}
}
}
但是這樣的隔離設計,卻無法滿足「必須按照先來後到去處理事件」這樣的行為要求。因為ThreadPool只是單純的為每一個WaitCallback委派,分派一條執行緒去做處理,而每條執行緒之間並沒有設定執行先後順序的能力。
這個問題困擾了我幾天,後來想到一個解法。在主執行緒跟事件執行緒之間,墊一層Queue做隔離,將要執行的WaitCallback委派都存放進這個Queue。並且通過Lock的機制,強迫一次只能有一個執行緒去處理Queue裡面的WaitCallback委派。透過這樣的設計,就可以完成將執行運作邏輯的主執行緒,跟發出事件的事件執行緒做隔離設計的工作。範例程式碼如下:
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var obj = new ClassB();
obj.NotifyArrived += new Action<int>(obj_NotifyArrived);
obj.Start();
Console.ReadLine();
obj.Stop();
}
static void obj_NotifyArrived(int data)
{
Console.WriteLine(data);
}
}
public class ClassB
{
// Fields
private bool _isRunning = true;
private readonly object _eventSyncRoot = new object();
private readonly Queue<WaitCallback> _eventDelegateQueue = new Queue<WaitCallback>();
// Methods
public void Start()
{
Thread thread = new Thread(this.Run);
thread.Start();
}
public void Stop()
{
_isRunning = false;
}
private void Run()
{
int i = 0;
while (_isRunning == true)
{
Thread.Sleep(100);
this.OnNotifyArrived(i++);
}
}
// Events
public event System.Action<int> NotifyArrived;
private void OnNotifyArrived(int data)
{
// Queue EventDelegate
WaitCallback eventDelegate = delegate(object state)
{
var handler = this.NotifyArrived;
if (handler != null)
{
handler(data);
}
};
lock (_eventDelegateQueue)
{
_eventDelegateQueue.Enqueue(eventDelegate);
}
// Run EventDelegate
WaitCallback handlerDelegate = delegate(object state)
{
lock (_eventSyncRoot)
{
WaitCallback runEventDelegate = null;
lock (_eventDelegateQueue)
{
if (_eventDelegateQueue.Count > 0)
{
runEventDelegate = _eventDelegateQueue.Dequeue();
}
}
if (runEventDelegate != null)
{
runEventDelegate(null);
}
}
};
ThreadPool.QueueUserWorkItem(handlerDelegate);
}
}
}
最後說一下,要完成這樣的隔離設計。不使用ThreadPool,而改用一個獨立Thread去處理WaitCallback委派,也是可行的設計。只不過獨立一個執行緒去處理,就需要額外增加管理這個獨立執行緒的工作,這就看每個人的選擇跟考量了。
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。