[英]Background consumer thread lifetime management best practices
我有一個C#類庫,它(延遲地)啟動了一個后台使用者線程,該線程監聽生產者/使用者隊列中要完成的任務。 可以從任何類型的.NET應用程序中使用該類庫,並且當前在ASP.NET MVC網站下使用該類庫。 在大多數情況下,消費者線程都會被阻塞,直到請求進入隊列。 任何給定的應用程序域都應該只有一個使用者線程。
我希望能夠在應用程序退出時正常關閉使用者線程,而不管它是哪種類型的應用程序,例如Windows Forms,Console或WPF應用程序。 應用程序代碼本身應該不知道該線程在等待終止的余地。
如何從類庫的角度解決此線程生存期問題? 那我可以綁定一些全局應用程序關閉事件來優雅地關閉線程嗎?
現在,由於它位於ASP.NET MVC應用程序域中,因此這實際上無關緊要,因為它們永遠也不會優雅地拆除。 但是現在我開始在計划終止的計划控制台應用程序任務中使用它,我希望一勞永逸地解決此問題。 控制台應用程序不會終止,因為使用者線程仍處於活動狀態並被阻止等待請求。 我已經公開了該類的公共靜態Thread屬性,以便在退出控制台應用程序時發出Abort()
調用,但是坦率地說,這令人作嘔。
任何指針將不勝感激! 同樣,我不想編寫Windows Forms或WPF或控制台應用程序特定的代碼來解決此問題。 每個類庫使用者都可以使用的一個很好的通用解決方案是最好的。
將線程的IsBackground
屬性設置為true
。 這樣就不會阻止進程結束。
http://msdn.microsoft.com/en-us/library/system.threading.thread.isbackground.aspx
或者,使用Interrupt
代替使用Abort
。 更少令人作嘔。
這有點棘手。 您已正確地希望避免中斷,因為這可能會在重要操作(寫入文件等)的中間終止線程。 設置IsBackground
屬性將具有相同的效果。
您需要做的是使您的阻塞隊列可以取消。 不幸的是,您沒有使用.NET 4.0,否則您可以使用BlockingCollection
類。 不用擔心。 您只需要修改您的自定義隊列,以便它定期輪詢以發出停止信號。 這是一個快速而骯臟的實現,我只是以阻塞隊列的規范實現為起點進行了總結。
public class CancellableBlockingCollection<T>
{
private Queue<T> m_Queue = new Queue<T>();
public T Take(WaitHandle cancel)
{
lock (m_Queue)
{
while (m_Queue.Count <= 0)
{
if (!Monitor.Wait(m_Queue, 1000))
{
if (cancel.WaitOne(0))
{
throw new ThreadInterruptedException();
}
}
}
return m_Queue.Dequeue();
}
}
public void Add(T data)
{
lock (m_Queue)
{
m_Queue.Enqueue(data);
Monitor.Pulse(m_Queue);
}
}
}
請注意Take
方法如何接受可以測試取消信號的WaitHandle
。 這可能與代碼其他部分中使用的等待句柄相同。 這里的想法是,等待句柄會在Take
內的某個時間間隔上輪詢,如果發出信號,則整個方法都會拋出異常。 假設可以將Take
扔到內部是可行的,因為這樣做是安全的,因為消費者不可能處於重要操作的中間。 此時,您可以只允許線程在發生異常時分解。 這是您的使用者線程的外觀。
ManualResetEvent m_Cancel = new ManualResetEvent(false);
CancellableBlockingCollection<object> m_Queue = new CancellableBlockingCollection<object>();
private void ConsumerThread()
{
while (true)
{
object item = m_Queue.Take(m_Cancel); // This will throw if the event is signalled.
}
}
您可以通過多種其他方式取消Take
方法。 我選擇使用WaitHandle
,但是可以使用例如簡單的bool
標志輕松完成。
更新:
正如史蒂文在評論中指出的那樣, Thread.Interrupt
實際上已經做到了這一點。 這將導致線程在阻塞調用上引發異常……這與本例中的代碼差不多,但是代碼更多。 Thread.Interrupt
一個警告是,它僅在.NET BCL中的固定阻塞調用期間起作用(例如WaitOne
等)。 因此,您將無法取消正在進行的長時間運行的計算,就像您使用一種更手動的方法(如此答案中的方法)一樣。 它絕對是裝在您口袋里的好工具,並且可能僅在您的特定情況下有用。
如果您使用的是.net 4.0,則可以使用CancelationToken
通知線程該正常關閉的時間了。 這是非常有用的,因為大多數“等待”命令都允許您向其傳遞令牌,如果在取消線程時線程處於等待狀態,它將自動退出等待狀態。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.