我正在寻找一个应用程序,我将处理几个集成并需要它们在线程中运行。我需要线程“向母舰报告(又称主循环)”。摘录:
class App
{
public delegate void StopHandler();
public event StopHandler OnStop;
private bool keepAlive = true;
public App()
{
OnStop += (() => { keepAlive = false; });
new Thread(() => CheckForStop()).Start();
new Thread(() => Update()).Start();
while (keepAlive) { }
}
private void CheckForStop()
{
while (keepAlive) if (Console.ReadKey().Key.Equals(ConsoleKey.Enter)) OnStop();
}
private void Update()
{
int counter = 0;
while (keepAlive)
{
counter++;
Console.WriteLine(string.Format("[{0}] Update #{1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), counter));
Thread.Sleep(3000);
}
}
}
这里的问题是变量keepAlive
。通过它的使用它不是线程安全的。我的问题是我如何才能使其线程安全。
如果Update
使用while(true)
代替keepAlive
而事件OnStop
中止线程,会变得安全(r)吗?
答案 0 :(得分:1)
使用对象和this method
class App
{
public delegate void StopHandler();
public event StopHandler OnStop;
private object keepAliveLock = new object();
private bool keepAlive = true;
....
private void Update()
{
int counter = 0;
while (true)
{
lock(keepAliveLock)
{
if(!keepAlive)
break;
}
counter++;
Console.WriteLine(string.Format("[{0}] Update #{1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), counter));
Thread.Sleep(3000);
}
}
}
请注意,每次访问keepAlive都需要锁定(由lock语句环绕)。处理死锁情况。
答案 1 :(得分:0)
就问题的具体措辞和示例代码(关于keepAlive
)而言,只需使用volatile
(对于简单的读取权限到bool
})。至于是否有其他方法可以改善您的代码,这是另一个故事。
private volatile bool keepAlive = true;
对于简单类型和访问,例如bool
,那么volatile
就足够了 - 这可以确保线程不会缓存该值。
对bool
的读取和写入 Atomic ,如C# Language Spec中所示。
以下数据类型的读写是原子的:bool,char, byte,sbyte,short,ushort,uint,int,float和reference types。
<强>此外强>
CLI规范的第I.6节,第12.6.6节说明:“符合CLI 应保证对正确对齐的内存进行读写访问 所有的位置都不大于本机字大小是原子的 对某个位置的写访问大小相同。
volatile关键字表示字段可能被修改 多个线程同时执行。是的领域 声明的volatile不受编译器优化的限制 假设由单个线程访问。这确保了最多 字段中始终存在最新值。
答案 2 :(得分:0)
我个人会改变这一点,等待bool变为false以使用ManualResetEvent。还使用System.Timers.Timer进行更新而不是循环:
private ManualResetEvent WaitForExit;
private ManualResetEvent WaitForStop;
public App()
{
WaitForExit = new ManualResetEvent(false);
WaitForStop = new ManualResetEvent(false);
new Thread(() => CheckForStop()).Start();
new Thread(() => Update()).Start();
WaitForExit.WaitOne();
}
private void CheckForStop()
{
while (true)
if (Console.ReadKey().Key.Equals(ConsoleKey.Enter))
{
WaitForStop.Set();
break;
}
}
private void Update()
{
int counter = 0;
Timer timer = new Timer(3000);
timer.Elapsed += Timer_Elapsed;
timer.AutoReset = true;
timer.Start();
WaitForStop.WaitOne();
timer.Stop();
WaitForExit.Set();
}
private int counter = 1;
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
Console.WriteLine(string.Format("[{0}] Update #{1}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), counter++));
}
答案 3 :(得分:0)
正如您自己注意到的那样,您在线程之间共享的可变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。
前提是,所有线程都由它们自己的类封装,并且只有该类可以与线程交互。在上面的示例中,这是通过将Action
放在并发队列上然后逐个执行它来完成的。