请考虑以下代码:
using (var mre = new ManualResetEvent(false))
{
var bgWkr = new BackgroundWorker();
bgWkr.DoWork += delegate(object sender, DoWorkEventArgs e)
{
var mrEvent = e.Argument as ManualResetEvent;
// some processing...
mrEvent.WaitOne();
// broadcast an event
};
bgWkr.RunWorkerAsync(mre);
// some other processing...
// hook into the same event
mre.Set();
}
假设产生的工人需要一点时间才能完成。我们将在工作线程完成并等待ManualResetEvent之前暂时离开使用块。我会假设当离开使用块时,mre会被关闭(假设它已被处理掉),这至少会引发异常。这是一个安全的假设吗?
这个示例可能不是ManualResetEvent的最佳示例,但它是为了说明我们在using块中访问匿名方法内的IDisposable对象并在退出using块后调用匿名方法的情况。是否存在一些保持一次性物体的机制?我不相信,但想要确认为什么(如果工作中有某种伏都教)或为什么不这样做。
干杯,
答案 0 :(得分:1)
是的,这段代码错了 - 结果没有真正定义,但是在mrEvent.WaitOne()
抛出异常是非常合理的,因为mrEvent
几乎肯定是现在 - 处置ManualResetEvent
。从技术上讲,工作线程有可能已经准备好了,而工作线程做的“一些处理......”比主线程执行“其他一些处理......”更快,但是:我不会依赖在上面。所以在大多数情况下:mrEvent
已经死了。
至于如何避免这种情况:也许这根本不是using
的情况。但是,由于工作线程执行WaitOne
,工作线程的WaitOne
无法在主线程执行mre.Set()
调用之前完成 - 所以您可以利用并将using
移动到工作人员:
var mre = new ManualResetEvent(false);
var bgWkr = new BackgroundWorker();
bgWkr.DoWork += delegate(object sender, DoWorkEventArgs e)
{
using(var mrEvent = e.Argument as ManualResetEvent)
{
// some processing...
mrEvent.WaitOne();
}
// broadcast an event
};
bgWkr.RunWorkerAsync(mre);
// some other processing...
// hook into the same event
mre.Set();
但是,请注意,这引发了一个有趣的问题:如果主线程在“其他一些处理......”中抛出异常会发生什么 - 永远不会达到对mre.Set()
的调用,并且工作者线程永远不会退出。您可能希望在mre.Set()
中执行finally
:
var mre = new ManualResetEvent(false);
try {
var bgWkr = new BackgroundWorker();
bgWkr.DoWork += delegate(object sender, DoWorkEventArgs e)
{
using(var mrEvent = e.Argument as ManualResetEvent)
{
// some processing...
mrEvent.WaitOne();
}
// broadcast an event
};
bgWkr.RunWorkerAsync(mre);
// some other processing...
}
finally {
// hook into the same event
mre.Set();
}
答案 1 :(得分:0)
为了回应我的评论(而不是提出问题的答案),我创建了一个类,一旦完成,就关闭ManualResetEvent,而不需要跟踪最后一个线程何时完成使用它。感谢Marc Gravell在WaitOne完成后关闭它的想法。如果其他人需要,我在这里公开它。
P.S。我受限于.NET 3.5 ...因此我没有使用ManualResetEventSlim。
干杯,
肖恩
public class OneTimeManualResetEvent
{
private ManualResetEvent _mre;
private volatile bool _closed;
private readonly object _locksmith = new object();
public OneTimeManualResetEvent()
{
_mre = new ManualResetEvent(false);
_closed = false;
}
public void WaitThenClose()
{
if (!_closed)
{
_mre.WaitOne();
if (!_closed)
{
lock (_locksmith)
{
Close();
}
}
}
}
public void Set()
{
if (!_closed)
_mre.Set();
}
private void Close()
{
if (!_closed)
{
_mre.Close();
_closed = true;
}
}
}