我可以使用简单的布尔值来控制长时间运行的线程吗?

时间:2018-08-15 19:39:13

标签: .net multithreading thread-safety

这些年来,我一直遵循一种简单的模式在我的应用程序中创建长期运行的工作程序(通常部署为Windows服务):

class LongRunningWorker
{
    public void Start() 
    {
        _isRunning = true;
        _thread = new Thread(DoWork);
        _thread.Start();
    }

    public void Stop() 
    {
        _isRunning = false;
        _thread.Join(TIMEOUT);
    }

    private void DoWork()
    { 
        while(_isRunning) 
        {
            // do work
            Thread.Sleep(INTERVAL);
        }

        // perform shutdown
    }
}

我现在想到我可能在isRunning字段上遇到比赛条件;此外,我通常忘记将其标记为易失性以防止缓存。所以,我的问题:

  1. 使用没有信号量或任何Interlocked调用的字段是否存在失败的风险?我会发生什么样的错误?

  2. 给出上一个问题的答案,是否有必要使用volatile?在我的示例中,确实是,但是如果问题1的答案是使用互斥锁或其他东西,我需要吗?

  3. 我也有一个想法,BackgroundWorker并不是解决此类问题的好方法-它是前端(我猜是Windows窗体)要使用的组件,而不是真正的异步组件程式。但这是真的吗?权衡是什么?还有另一个类可以消除我正在编写的样板吗?

1 个答案:

答案 0 :(得分:0)

几年来,我也使用了稍微改进的版本。在许多项目中,效果很好。我使用ManualResetEvent来立即发出停止事件的信号,并使用ManualResetEvent.WaitOne(INTERVAL, false)来延迟工作线程。

对于.NET 4及更高版本,使用CancellationToken更为方便,这是一个简化的示例:

  class LongRunningWorker : IDisposable
  {
    private volatile bool            _disposing    = false;
    private volatile bool            _isDisposed   = false;
    private CancellationTokenSource  _cancelSource = null;
    private System.Threading.Thread  _thread       = null;

    public LongRunningWorker() 
    {
    }            

    public void Start() 
    {
      if(_disposing || _isDisposed)
        throw new ObjectDisposedException("LongRunningWorker");

      RecreateCancellationSource();

      _thread              = new Thread(new ParameterizedThreadStart(DoWork));
      _thread.IsBackground = true; // do not block process termination
      _thread.Start((object)_cancelSource.Token); // copy cancellation token by value, used as the callback argument
    }

    public void Stop() 
    {
      if(_disposing || _isDisposed)
        return;

      _cancelSource.Cancel();
    }

    private void DoWork(object state)
    {
      CancellationToken cancelToken = (CancellationToken)state;

      while(!cancelToken.IsCancellationRequested) 
      {
        try
        {
          // do work

          // thread delay or cancellation
          if(cancelToken.WaitOne(INTERVAL))
            break;
        }
        catch (OperationCanceledException)
        { // expected exception on cancellation
          break;
        }
        catch(Exception ex)
        { // your exception handling
        }
      }

      // perform shutdown
    }

    private void RecreateCancellationSource()
    {
      CancellationTokenSource oldSource;

      try
      {        
        oldSource = Interlocked.Exchange<CancellationTokenSource>(ref _cancelSource, new CancellationTokenSource());

        if (oldSource != null)
        {
          oldSource.Cancel();
          oldSource.Dispose();
        }
      }
      catch (Exception ex)
      {
        Debug.WriteLine("LongRunningWorker.DisposeCancellationSource exception: " + ex);
      }
      finally
      {
        oldSource = null;
      }
    }

    private void DisposeCancellationSource()
    {
      CancellationTokenSource oldSource;

      try
      {
        oldSource = Interlocked.Exchange<CancellationTokenSource>(ref _cancelSource, null);

        if (oldSource != null)
        {
          oldSource.Cancel();
          oldSource.Dispose();
        }
      }
      catch (Exception ex)
      {
        Debug.WriteLine("LongRunningWorker.DisposeCancellationSource exception: " + ex);
      }
      finally
      {
        oldSource = null;
      }
    }

    public void Dispose()
    {
      _disposing = true;

      Stop();
      DisposeCancellationSource();

      _isDisposed = true;
      _disposing  = false;
    }
  }