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

时间:2009-12-17 20:05:47

标签: c# .net multithreading

我需要一个.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代码不是线程安全的,所以我需要在后台线程上同步我的所有调用。

编辑:我的代码的最后一刻更改显然不正确,修复了它。

6 个答案:

答案 0 :(得分:4)

我认为你想要这个,因为你不希望任何这些'工作'同时运行,这看起来像是一个有效的要求。但它与“所有在同一个单线程上”略有不同。

(Fx4)任务并行库有一个'ContinuesWith'结构,可以解决类似的问题,但具有不同的接口。

所以我认为你必须(继续)推出自己的。一些批评:

您在锁外检查m_Queue.Count,这可能是安全的,因为您只有1个消费者,但我会将其折叠到锁中。

此外,您可以使用Monitor.Wait()和Monitor.Pulse()替换AutoResetEvent。这是'更轻'(所有托管代码),并且它与lock(== Monitor.Enter / .Exit)一起运行良好。

答案 1 :(得分:3)

我将忽略你是否应该这样做的问题,只是给你反馈你的代码。大多数是样式问题或使用最佳实践的建议,但其他是需要修复的错误。

  1. 不要使用匈牙利表示法(m_ *)。没必要。
  2. 访问m_Terminate时需要锁定。或者至少它需要是不稳定的。
  3. 为什么使用Action<object>然后传递null作为参数?如果你不想要参数,你不能只使用ThreadStart吗?固定
  4. WorkItem应该是不可变的。使用readonly成员或具有私有设置器的属性。
  5. 缺少错误处理。如果您的某个工作项操作引发异常,则会停止整个线程池的工作(假设它不会将整个应用程序关闭)。
  6. 我必须同意格雷格的评论。这不是一个线程池。这是一个工作队列。该类的名称应该反映出来。
  7. 您应该验证参数callbackQueueUserWorkItem不为空,以避免NullReferenceExceptionfail fast)中的ThreadMain循环。
  8. 如果在ObjectDisposedException被调用后调用QueueUserWorkItem,您应该抛出Dispose

答案 2 :(得分:1)

我假设你真的有理由想要一个只有一个线程的线程池。在这种情况下,我只发现一个主要缺陷:当你总是通过Action<T>时,为什么使用null?只需使用Action,它不带任何参数。

答案 3 :(得分:1)

它在其中一条评论中,但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 ...
      }
   }
}

通过这种方式,你只有一个后台线程,它只是循环等着你做你的事。

答案 4 :(得分:1)

呵呵,我最近写了一些非常相似的东西。

使用AutoResetEvent进行同步时,如果对工作项的排队速度超过处理速度,则最终会得到队列中的项目,但没有事件触发项目的处理。我建议使用信号量,以便您可以保持已发出信号的次数,以便处理所有工作项。

答案 5 :(得分:0)

我认为你正在重新发明轮子。提供的System.Threading.ThreadPool提供此功能。我会使用它并回到编写实际的应用程序逻辑。