簡體   English   中英

后台使用者線程生命周期管理最佳實踐

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM