一个允许安全启动/停止线程的基础类
这是一篇简短的文章,介绍如何将一个漂亮易用的基类应用于解决类支持内部工作线程且线程安全的老问题。
引言
这是一篇关于如何将一个简洁易用的基础类应用于一个古老问题——使你的类以线程安全的方式支持内部工作线程的文章。我想要的是一个基础类,它能让内部线程合理地启动和停止,并提供一个抽象的回调方法,以便灵活地执行“工作单元”。
背景
通常,你可能需要一个拥有自己工作线程的类。好吧,我需要。也许只是我。而且我经常看到人们使用布尔值来指示线程正在运行,或者线程应该停止。例如:
private static Thread _thread; private static bool _isRunning; static void Main() { _thread = new Thread(threadMethod); _thread.Start(); _isRunning = true; Console.WriteLine("Any key to stop"); Console.ReadKey(); _isRunning = false; while (_thread.IsAlive) { Console.WriteLine("Wait for stop"); Thread.Sleep(200); } } static void threadMethod() { while (_isRunning) { Console.WriteLine("Thread running"); Thread.Sleep(1000); } }
这本身是可以的,但不够优雅。它不是线程安全的。简而言之,“还可以做得更好”。
Using the Code
- 所以,设计要求(至少在我的脑海中)是:
- 抽象基础类。
ManualResetEvent
对象来指示线程正在运行,以及线程已被要求停止。 - 线程安全的启动和停止方法。
- 一个由继承类实现的抽象回调,该回调将在线程以每个“刻度”为单位被调用。
- 一些有用的事件,比如“线程完成”事件。
此外,我当前的用例要求工作线程能够重复执行小块的工作,以便将大型过程分解,使其能够及时停止。例如,考虑一个将来自数据源的股票数据加载到数据库的过程。每个“工作单元”可能是检查是否有给定股票的新数据可用,如果有,则从数据源读取并写入数据库。(显然这很简化,但在某些情况下,你无法获得设计良好的第三方产品...)。你希望每个股票在一个工作单元中读取,因为读取 100 左右的股票意味着线程的“停止”命令需要很长时间才能响应...
所以,废话不多说,这是基础类目前的样子
public abstract class ManagedThread { private Thread thread; protected ManualResetEvent mreAskStop; private ManualResetEvent mreInformStopped; public delegate void ThreadFinishedEventHandler(); public event ThreadFinishedEventHandler ThreadFinishedEvent; private object _lockObject = new object(); private void RaiseFinishedEvent() { if (ThreadFinishedEvent != null) { ThreadFinishedEvent(); } } public ManagedThread(string name) { Name = name; mreAskStop = new ManualResetEvent(false); mreInformStopped = new ManualResetEvent(false); } public virtual void Start() { lock (_lockObject) { if (thread != null) { // already running state return; } mreAskStop.Reset(); mreInformStopped.Reset(); thread = new Thread(WorkerCallback); thread.IsBackground = true; thread.Start(); } } protected abstract void DoUnitOfWork(ref int tick); protected int TickDefaultMilliseconds = 1000; protected int TickCount; protected virtual void WorkerCallback() { for (; ; ) { DoUnitOfWork(ref TickCount); TickCount--; Thread.Sleep(TickDefaultMilliseconds); if (mreAskStop.WaitOne(0, true)) { mreInformStopped.Set(); break; } } RaiseFinishedEvent(); } public bool HasStopped() { return mreInformStopped.WaitOne(1, true); } public bool IsAlive { get { return thread != null && thread.IsAlive; } } public virtual void Stop() { lock (_lockObject) { // todo fix faulted state exception if (thread != null && thread.IsAlive) // thread is active { mreAskStop.Set(); while (thread.IsAlive) { if (mreInformStopped.WaitOne(100, true)) { break; } } thread = null; } } } public string Name { get; private set; } }
关注点
- 工作线程的退出将调用“线程完成”事件,这很好。
- 线程被标记为工作线程,以便在拥有进程退出时一起退出。
- 有一个锁对象用于防止重入。
- 默认的毫秒刻度可以更改,以使对
DoUnitOfWork
的调用粒度更粗。
用法
以之前提到的股票读取器为例,我们可以有一个派生类ReadStocks
,如下所示:public class ReadStock : ManagedThread { private const int readStockIntervalInSeconds = 5; public ReadStock() : base("Read Stock"){} protected override void DoUnitOfWork(ref int tick) { if (tick == 0) { tick = readStockIntervalInSeconds; // check 3rd party product, is there new data? // if yes: // read the stock from the 3rd party product // write it to the database } } }
并像这样调用它:
ReadStock readStock = new ReadStock(); readStock.Start(); Console.WriteLine("Any key to stop"); Console.ReadKey(); readStock.Stop();
最后,如果你希望它只运行一次,或者在达到一定数量的迭代后停止,可以在 DoUnitOfWork
方法内部调用 Stop()
命令
public class ReadStock : ManagedThread { public ReadStock() : base("Do one thing only"){} protected override void DoUnitOfWork(ref int tick) { // do something only once ... // call stop to exit thread Stop(); } }
或者
public class ReadStock : ManagedThread { private const int eventIntervalInSeconds = 20; private const int numberOfTimesToDoThis = 5; private int eventCounter; public ReadStock() : base("Do it five times") { eventCounter = 0; } protected override void DoUnitOfWork(ref int tick) { if (tick == 0) { tick = readStockIntervalInSeconds; // Do the thing you want to do ... // and stop if its done enough times if (eventCounter++ > numberOfTimesToDoThis) { Stop(); } } } }