正如您自己所注意到的,您在线程之间共享的可变keepAlive 变量会导致您头疼。我的建议是删除它。更一般地说:
停止在线程之间共享可变状态
基本上所有多线程问题都来自共享可变状态的线程。
在执行打印的对象上将keepAlive 设为私有实例变量。让那个类实例化它自己的线程,并让所有发送到对象的消息都放在一个 ConcurrentQueue 中:
class Updater
{
// All messages sent to this object are stored in this concurrent queue
private ConcurrentQueue<Action> _Actions = new ConcurrentQueue<Action>();
private Task _Task;
private bool _Running;
private int _Counter = 0;
// This is the constructor. It initializes the first element in the action queue,
// and then starts the thread via the private Run method:
public Updater()
{
_Running = true;
_Actions.Enqueue(Print);
Run();
}
private void Run()
{
_Task = Task.Factory.StartNew(() =>
{
// The while loop below quits when the private _Running flag
// or if the queue of actions runs out.
Action action;
while (_Running && _Actions.TryDequeue(out action))
{
action();
}
});
}
// Stop places an action on the event queue, so that when the Updater
// gets to this action, the private flag is set.
public void Stop()
{
_Actions.Enqueue(() => _Running = false);
}
// You can wait for the actor to exit gracefully...
public void Wait()
{
if (_Running)
_Task.Wait();
}
// Here's where the printing will happen. Notice that this method
// puts itself unto the queue after the Sleep method returns.
private void Print()
{
_Counter++;
Console.WriteLine(string.Format("[{0}] Update #{1}",
DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), _Counter));
Thread.Sleep(1000);
// Enqueue a new Print action so that the thread doesn't terminate
if (_Running)
_Actions.Enqueue(Print);
}
}
现在我们只需要一种方法来停止线程:
class Stopper
{
private readonly Updater _Updater;
private Task _Task;
public Stopper(Updater updater)
{
_Updater = updater;
Run();
}
// Here's where we start yet another thread to listen to the console:
private void Run()
{
// Start a new thread
_Task = Task.Factory.StartNew(() =>
{
while (true)
{
if (Console.ReadKey().Key.Equals(ConsoleKey.Enter))
{
_Updater.Stop();
return;
}
}
});
}
// This is the only public method!
// It waits for the user to press enter in the console.
public void Wait()
{
_Task.Wait();
}
}
将它们粘合在一起
我们现在真正需要的是一个main 方法:
class App
{
public static void Main(string[] args)
{
// Instantiate actors
Updater updater = new Updater();
Stopper stopper = new Stopper(updater);
// Wait for the actors to expire
updater.Wait();
stopper.Wait();
Console.WriteLine("Graceful exit");
}
}
延伸阅读:
上述为线程封装可变状态的方法称为Actor Model。
前提是,所有线程都被自己的类封装,只有那个类才能与线程交互。在上面的例子中,这是通过将Actions 放在一个并发队列中然后一个一个地执行来完成的。