繁体   English   中英

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

[英]Proper way to clean up a permanent thread in C#

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

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

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

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

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

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

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


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

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

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

而且我特别不想要关心如果我创建一个动画师,我必须跟踪它并在我的程序退出时调用一个特殊方法,与其他所有对象形成对比。 它应该是图书馆实施者的关注点,而不是图书馆的消费者。


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

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

也许将T hread.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 =)

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM