以下场景:我有一个包含2种方法的课程:StartLoop
和StopLoop
。
StartLoop
启动一个循环,轮询一些数据并将其写入数据结构,之后,它会引发一个事件。重复这一过程,直到调用StopLoop
为止。
StopLoop
停止循环并清除数据结构。
现在可能会发生这样的情况,即循环正在向数据结构写入内容StopLoop
被执行并清除数据结构。为了防止同时访问,我会锁定。
不幸的是,当我在循环启动后调用StopLoop
时,我的应用程序挂起了锁。
我的简化代码:
class Foo
{
public event EventHandler Event;
private bool _runLoop;
private List<int> _fooList = new List<int>() { 0 }; //my data structure
static readonly object _myLocker = new object();
public void StartLoop()
{
_runLoop = true;
new Thread(() =>
{
while (_runLoop)
{
lock (_myLocker)
{
Event(this, new EventArgs()); //call event
_fooList[0] = 1; //do some writing on structure
}
}
}).Start();
}
public void StopLoop()
{
lock (_myLocker) //<--- application hangs here
{
_runLoop = false; //end loop
_fooList = null; //clear structure
}
}
}
在我的窗口(它是一个WPF应用程序)我将我的事件注册到跟随处理程序:
void foo_Event(object sender, EventArgs e)
{
//append an "a" for each loop
Dispatcher.BeginInvoke(new Action(() => { uxDisplayTest.Text += "a"; }));
//a "DoEvent" for WPF to keep the window responive
Dispatcher.Invoke(new Action(() => { }), System.Windows.Threading.DispatcherPriority.ContextIdle, null);
}
为什么我打电话StopLoop
时我的申请会冻结?我该如何解决?
答案 0 :(得分:1)
我使用ManualResetEvent
来表示它应该停止的线程。这样您就不需要锁定StopLoop
:
private Thread _myThread = null;
private ManualResetEvent _stopThread = new ManualResetEvent(false);
public void StartLoop()
{
_myThread = new Thread(() =>
{
while (!_stopThread.WaitOne(1))
{
Event(this, new EventArgs()); //call event
lock (_myLocker)
{
_fooList[0] = 1; //do some writing on structure
}
}
});
_myThread.Start();
}
public void StopLoop()
{
stopThread.Set();
_myThread.Join();
// Why clear the structure? Rather re-init when restarting the threads
//_fooList = null; //clear structure
}
请注意,如果您正在触发的事件在UI线程的上下文中执行任何操作,则此操作仍可能会阻止。也许你根本不需要Join
。就个人而言,我在其循环中暂停了一些线程以降低CPU使用率。
您会注意到我也移动了锁,因此只能访问该阵列。在锁中调用事件是个坏主意。这必将迟早造成死锁。此外,规则是尽快锁定资源。
关于你的评论:代码背后的想法是等到处理程序完成(否则我的事件太快,以至于处理程序无法处理所有事件)。
这不可能发生。所有附加的事件处理程序将在Event(this, new EventArgs());
中执行,并且只有在此之后才会执行下一行代码。此外,锁定根本不会改变这种行为。