C# 使用块和引用 IDisposable 对象的匿名方法
本文关键字:对象 方法 IDisposable 引用 | 更新日期: 2023-09-27 18:31:20
请考虑以下代码:
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 块后调用匿名方法的情况。是否有某种机制可以固定一次性物品?我不这么认为,但希望得到一些确认,为什么(如果有某种巫毒教在起作用)或为什么不这样做。
干杯
是的,这段代码是错误的 - 结果并没有真正定义,但它在mrEvent.WaitOne()
抛出异常是相当合理的,因为mrEvent
几乎可以肯定现在已处置ManualResetEvent
。从技术上讲,工作线程有可能全部准备就绪,并且工作线程进行了"一些处理..."比主线程执行"其他一些处理..."的速度更快,但是:我不会依赖它。所以在大多数情况下:mrEvent
已经死了。
至于如何避免这种情况:也许这根本不是using
的情况。但是,由于工作线程执行WaitOne
,因此在主线程执行mre.Set()
调用之前,工作线程的WaitOne
无法完成 - 因此您可以利用这一点并将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()
的调用,并且工作线程永远不会退出。您可能希望在finally
中执行mre.Set()
:
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();
}
为了回应我的评论(而不是提出问题的答案),我创建了一个类来关闭 ManualResetEvent 一旦完成它,而无需跟踪最后一个线程何时完成使用它。感谢Marc Gravell的想法,一旦WaitOne完成就关闭它。如果其他人需要它,我在这里公开它。
附言我被限制在 .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;
}
}
}