簡體   English   中英

在.NET中的后台線程上運行Legacy,Non-Reentrant Code

[英]Running Legacy, Non-Reentrant Code on a Background Thread in .NET

我在.NET應用程序中需要一個線程工作者 - .NET有幾個類,比如線程池等,但我找不到任何在單個線程上運行的東西,這在我的情況下是一個要求。

所以我自己去寫了一篇文章,但是這些東西非常棘手,我確信我有些不對勁。 任何人都可以改進它或指向我已經寫過的類似方向嗎?

public class ThreadWorker : IDisposable
{
    public ThreadWorker()
    {
        m_Thread = new Thread(ThreadMain);
        m_Thread.IsBackground = true;
        m_Thread.Name = "Worker Thread";
        m_Thread.Start();
    }

    public void Dispose()
    {
        if (!m_Terminate)
        {
            m_Terminate = true;
            m_Event.Set();
            m_Thread.Join();
        }
    }

    public void QueueUserWorkItem(Action<object> callback, object data)
    {
        lock (m_Queue) m_Queue.Enqueue(new WorkItem(callback, data));
        m_Event.Set();
    }

    void ThreadMain()
    {
        while (!m_Terminate)
        {
            if (m_Queue.Count > 0)
            {
                WorkItem workItem;
                lock (m_Queue) workItem = m_Queue.Dequeue();
                workItem.Callback(workItem.Data);
            }
            else
            {
                m_Event.WaitOne();
            }
        }
    }

    class WorkItem
    {
        public WorkItem(Action<object> callback, object data)
        {
            Callback = callback;
            Data = data;
        }

        public Action<object> Callback;
        public object Data;
    }

    AutoResetEvent m_Event = new AutoResetEvent(false);
    Queue<WorkItem> m_Queue = new Queue<WorkItem>();
    Thread m_Thread;
    bool m_Terminate;
}

來吧,把它分開吧!

請停止詢問我是否需要這個:是的我做 - 我有遺留的C代碼不是線程安全的,所以我需要在后台線程上同步我的所有調用。

編輯:最后一分鍾更改我的代碼顯然不正確,修復它。

我假設你想要這個,因為你不希望任何這些“工作”同時運行,這看起來像一個有效的要求。 但它與“所有在同一個單線程上”略有不同。

(Fx4)任務並行庫有一個'ContinuesWith'結構,可以解決類似的問題,但具有不同的接口。

所以我認為你必須(繼續)推動自己。 一些批評:

你在鎖外檢查m_Queue.Count ,這可能是安全的,因為你只有1個消費者,但我會把它折疊成鎖。

您還可以使用Monitor.Wait()和Monitor.Pulse()替換AutoResetEvent。 這是“更輕”(所有托管代碼),並且它與lock(== Monitor.Enter / .Exit)一起運行良好。

我將忽略你是否應該這樣做的問題,只是給你反饋你的代碼。 大多數是樣式問題或使用最佳實踐的建議,但其他是需要修復的錯誤。

  1. 不要使用匈牙利表示法(m_ *)。 這不是必需的。
  2. 訪問m_Terminate時需要鎖定。 或者至少它需要是不穩定的。
  3. 為什么使用Action<object>然后將null作為參數傳遞? 如果你不想要參數,你不能只使用ThreadStart嗎? 固定
  4. WorkItem應該是不可變的。 使用readonly成員或具有私有setter的屬性。
  5. 缺少錯誤處理。 如果您的某個工作項操作引發異常,則會停止整個線程池的工作(假設它不會占用整個應用程序)。
  6. 我不得不同意格雷格的評論。 這不是一個線程池。 這是一個工作隊列。 該類的名稱應該反映出來。
  7. 您應該驗證QueueUserWorkItem的參數callback是否為null,以避免ThreadMain循環中的NullReferenceException快速失敗 )。
  8. 如果在調用Dispose之后調用QueueUserWorkItem則應拋出ObjectDisposedException

我假設你真的有理由想要一個只有一個線程的線程池。 在這種情況下,我只發現一個主要缺陷:當你總是傳遞null時,為什么使用Action<T> 只需使用Action ,它不帶任何參數。

它在其中一個評論中,但BackgroundWorker,本質上是你想要的一個很好的實現。 雖然你可以開始其中幾個,但在你的情況下只需啟動一個。

其次,您實際上可以通過更改策略使用ThreadPool:

ThreadPool.QueueUserWorkItem(myWorkerProcess);

然后:

void myWorkerProcess(object state) {
   WorkItem workItem;

   while (!m_Terminate) {
      m_Event.WaitOne(5000);
      if (m_Queue.Count > 0) {
         lock (m_Queue) workItem = m_Queue.Dequeue();
         // ... do your single threaded operation here ...
      }
   }
}

通過這種方式,你只有一個后台線程,它只是循環等待你做你的事情。

呵呵,我最近寫了一些非常類似的東西。

使用AutoResetEvent進行同步時,如果對工作項的排隊速度超過處理速度,則最終會得到隊列中的項目,但不會觸發事件處理。 我建議使用信號量,以便您可以保持已發出信號的次數,以便處理所有工作項。

我想你正在重新發明輪子。 提供的System.Threading.ThreadPool提供此功能。 我會使用它並回到編寫實際的應用程序邏輯。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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