多年来,我一直遵循一个简单的模式在我的应用程序中创建长时间运行的工作线程(通常部署为 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
场上有一个比赛条件;此外,我通常忘记将其标记为易失性以防止缓存。所以,我的问题:
在没有信号量或任何类型的
Interlocked
调用的情况下使用该字段是否存在失败的风险?我可能会遇到什么样的错误?鉴于上一个问题的答案,使用
volatile
是必要的吗?在我的示例中,它当然是,但是如果问题 1 的答案是使用互斥锁或其他东西,我需要吗?我还有一个想法,即
BackgroundWorker
不是此类问题的好解决方案 - 它是前端(我猜是Windows窗体(使用的组件,而不是真正的异步程序。但这是真的吗?有什么权衡?是否有另一个类可以删除我正在编写的样板?
几年来,我也使用了一个稍微改进的版本。这在许多项目中都非常有效。我使用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;
}
}