我有一个对象,一个时间线,它封装了一个线程。可以在时间线上安排活动;线程将等到执行任何事件的时间,执行它,然后返回睡眠(对于(a)到达下一个事件所需的时间,或者(b)如果没有更多事件,则无限期地)。
睡眠由 WaitEventHandle 处理,当事件列表改变(因为可能需要调整睡眠延迟)或应该停止线程(因此线程可以优雅地终止)时触发。
析构函数调用 Stop(),我什至实现了 IDisposable,Dispose() 也调用了 Stop()。
尽管如此,当我在表单应用程序中使用这个组件时,当我关闭表单时,我的应用程序永远不会正确关闭。出于某种原因,Stop() 永远不会被调用,因此在.NET 决定等待所有线程完成之前,我的对象的析构函数都不会触发,也不会调用 Dispose() 方法。
我想解决方案是我自己在 FormClose 事件上显式调用 Dispose(),但是由于这个类将在一个库中,而且它实际上是更深的一层(也就是说,应用程序开发人员永远不会真正看到时间轴类),这看起来很丑陋,对于应用程序开发人员来说是一个额外的(不必要的)陷阱。我通常在资源释放成为问题时使用的 using() 子句不适用,因为这将是一个长期存在的对象。
一方面,我可以理解 .NET 在进行最后一轮垃圾回收之前会希望等待所有线程完成,但在这种情况下会产生非常笨拙的情况。
如何在不向我的库的使用者添加要求的情况下正确地清理我的线程?换句话说,如何让 .NET 在应用程序退出时通知我的对象,但在它等待所有线程完成之前?
编辑:回应人们说客户端程序可以知道线程是可以的:我恭敬地不同意。
正如我在原始帖子中所说,线程隐藏在另一个对象(动画师)中。我为另一个对象实例化了一个 Animator,并告诉它执行动画,例如“使此灯闪烁 800 毫秒”。
作为 Animator 对象的使用者,我不关心 Animator 如何确保灯光闪烁 800 毫秒。它会启动一个线程吗?我不在乎。它是否创建一个隐藏窗口并使用系统计时器(ew)?我不在乎。它会雇用侏儒来打开和关闭我的灯吗?我不在乎。
而且我特别不想关心如果我创建了一个 Animator,我必须跟踪它并在我的程序退出时调用一个特殊的方法,这与其他所有对象不同。这应该是图书馆实现者的关注点,而不是图书馆消费者的关注点。
编辑:代码实际上足够短,可以显示。我将它包括在内以供参考,没有将事件添加到列表的方法:
internal class Timeline : IDisposable {
private Thread eventThread;
private volatile bool active;
private SortedList<DateTime, MethodInvoker> events = new SortedList<DateTime,MethodInvoker>();
private EventWaitHandle wakeup = new EventWaitHandle(false, EventResetMode.AutoReset);
internal Timeline() {
active = true;
eventThread = new Thread(executeEvents);
eventThread.Start();
}
~Timeline() {
Dispose();
}
private DateTime NextEvent {
get {
lock(events)
return events.Keys[0];
}
}
private void executeEvents() {
while (active) {
// Process all events that are due
while (events.Count > 0 && NextEvent <= DateTime.Now) {
lock(events) {
events.Values[0]();
events.RemoveAt(0);
}
}
// Wait for the next event, or until one is scheduled
if (events.Count > 0)
wakeup.WaitOne((int)(NextEvent - DateTime.Now).TotalMilliseconds);
else
wakeup.WaitOne();
}
}
internal void Stop() {
active = false;
wakeup.Set();
}
public void Dispose() {
Stop();
}
}