在C#中清理永久线程的正确方法

时间:2009-02-17 21:43:36

标签: c# multithreading termination

我有一个封装线程的对象,即时间轴。活动可以在时间表上安排;线程将等待,直到执行任何事件,执行它,然后再回到睡眠状态(对于(a)到达下一个事件所花费的时间或(b)无限期地没有更多事件)。

使用WaitEventHandle处理休眠,当事件列表被更改时触发(因为可能需要调整睡眠延迟)或者应该停止线程(因此线程可以正常终止)。

析构函数调用Stop(),我甚至实现了IDisposable,Dispose()也调用了Stop()。

但是,当我在表单应用程序中使用此组件时,关闭表单时我的应用程序将永远不会正常关闭。出于某种原因,从不调用Stop(),因此在 .NET决定等待所有线程完成之前,我的对象的析构函数都没有触发,也没有调用Dispose()方法,

我想解决方案是在FormClose事件上显式调用Dispose(),但由于这个类将在一个库中,它实际上是一个更深的层(即,应用程序开发人员永远不会真正看到Timeline类),这看起来非常难看,并且是应用程序开发人员的额外(不必要的)问题。 using()子句,我通常在资源释放成为问题时使用,但不适用,因为这将是一个长期存在的对象。

一方面,我可以理解.NET会在进行最后一轮垃圾收集之前等待所有线程完成,但在这种情况下会产生非常笨拙的情况。

如何在不向库的消费者添加要求的情况下正确清理我的线程?换句话说,如何在应用程序退出时让.NET通知我的对象,但在它等待所有线程完成之前?


编辑:回应人们说客户端程序可以知道该线程:我恭敬地不同意。

正如我在原帖中所说,线程隐藏在另一个对象(Animator)中。我为另一个对象实例化Animator,并告诉它执行动画,例如“将此灯闪烁800毫秒”。

作为Animator对象的消费者,我并不关心Animator如何确保灯光正好闪烁800毫秒。它是否开始一个线程?我不在乎。它是否会创建隐藏窗口并使用系统计时器(ew)?我不在乎。是否雇用小部件来打开和关闭灯?我不在乎。

而我尤其不想要关心如果我创建了一个Animator,我必须跟踪它并在我的程序退出时调用一个特殊的方法,与之相反其他对象。它应该是图书馆实施者的关注点,而不是图书馆消费者。


编辑:代码实际上足够短显示。我将其包含在参考中, sans 将事件添加到列表中的方法:

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();
    }
}

5 个答案:

答案 0 :(得分:4)

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

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

另一种选择是使用Interrupt方法将其唤醒。只需确保捕获正在中断的线程中的ThreadInterruptedException,并确保它在发生时关闭。

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

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

答案 1 :(得分:3)

我认为要求客户端停止()线程完全关闭是不合理的。有一些方法可以创建线程,其持续执行不会阻止应用程序退出(虽然我没有详细介绍)。但是期望启动和终止工作线程并不会给客户带来太大的负担。

答案 2 :(得分:1)

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

答案 3 :(得分:1)

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

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

答案 4 :(得分:0)

实施IDisposable properly,包括实现调用Dispose(true)的终结器。然后,您可以根据需要进行任何清理,包括必要时停止线程。