3

我有一个对象,一个时间线,它封装了一个线程。可以在时间线上安排活动;线程将等到执行任何事件的时间,执行它,然后返回睡眠(对于(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();
    }
}
4

5 回答 5

4

也许将 T thread.IsBackground属性设置为 true?

eventThread = new Thread(executeEvents);
eventThread.IsBackground = true;
eventThread.Start();

另一种选择是使用中断方法将其唤醒。只要确保你ThreadInterruptedException在你正在中断的线程中捕捉到,并且当它发生时它会关闭。

active = false;
eventThread.Interrupt();
try { eventThread.Join(); }   // Wait for graceful shutdown
catch (Exception) { }

不太确定你的那个EventWaitHandle是如何工作的......当我做过一次类似的事情时,我只是使用了常规Thread.Sleep=)

于 2009-02-17T21:55:10.870 回答
3

我认为要求客户端 Stop() 关闭线程完全不是不合理的。有一些方法可以创建线程,这些线程的继续执行不会阻止应用程序退出(尽管我没有想到细节)。但是期望启动和终止一个工作线程对客户端来说并没有太大的负担。

于 2009-02-17T21:50:30.397 回答
1

如果没有客户的合作,就无法让 .NET 通知您的线程。如果您将库设计为具有长时间运行的后台线程,则必须将客户端应用程序设计为了解它。

于 2009-02-17T21:53:37.160 回答
1

Application::ApplicationExit是一个静态事件,是否可以接受监听并进行特殊清理工作?

实现 IDisposable 应该足以表明您的客户应该在“使用”块中使用您的类。

于 2009-02-17T22:27:30.807 回答
0

正确实现 IDisposable ,包括实现调用 Dispose(true) 的终结器。然后,您的 Animator 对象可以进行任何它希望进行的清理,包括在必要时停止线程。

于 2009-02-17T23:33:23.567 回答