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

发布于 2024-07-13 23:29:29 字数 2579 浏览 13 评论 0原文

我有一个对象,一个时间轴,它封装了一个线程。 可以在时间线上安排事件; 线程将等待,直到需要执行任何事件,执行它,然后返回睡眠状态((a)到达下一个事件所需的时间或(b)如果没有更多事件则无限期)。

休眠是通过 WaitEventHandle 来处理的,当事件列表发生更改(因为可能需要调整休眠延迟)或线程应该停止时(以便线程可以正常终止),就会触发该休眠。

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

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

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

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

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


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

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

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

而且我尤其不想关心如果我创建了一个动画器,我必须跟踪它并在程序退出时调用一个特殊的方法,这与其他所有对象不同。 这应该是图书馆实施者关心的问题,而不是图书馆消费者的问题。


编辑:代码实际上足够短以显示。 我将包含它以供参考,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();
    }
}

I have an object, a Timeline, that encapsulates a thread. Events can be scheduled on the timeline; the thread will wait until it is time to execute any event, execute it, and go back to sleep (for either (a) the time it takes to get to the next event or (b) indefinitely if there are no more events).

The sleeping is handled with a WaitEventHandle, which is triggered when the list of event is altered (because the sleep delay may need to be adjusted) or when the thread should be stopped (so the thread can terminate gracefully).

The destructor calls Stop(), and I've even implemented IDisposable and Dispose() also calls Stop().

Still, when I use this component in a forms application, my application will never shut down properly when I close the form. For some reason, Stop() is never called, so neither my object's destructor triggers, nor is the Dispose() method called, before .NET decides to wait for all threads to finish.

I suppose the solution would be to explicitly call Dispose() myself on the FormClose event, but since this class is going to be in a library, and it is actually a layer deeper (that is, the application developer will never actually see the Timeline class), this seems very ugly and an extra (unnecessary) gotcha for the application developer. The using() clause, which I would normally use when resource release becomes an issue, doesn't apply as this is going to be a long-lived object.

On the one hand, I can understand that .NET will want to wait for all threads to finish before it does its final round of garbage collection, but in this case that produces a very clumsy situation.

How can I make my thread clean up after itself properly without adding requirements to consumers of my library? Put another way, how can I make .NET notify my object when the application is exiting, but before it will wait for all threads to finish?


EDIT: In response to the people saying that it is ok for the client program to be aware of the thread: I respectfully disagree.

As I said in my original post, the thread is hidden away in another object (an Animator). I instantiate an Animator for another object, and I tell it to perform animations, such as "blink this light for 800ms".

As a consumer of the Animator object, I do not care how the Animator makes sure that the light blinks for exactly 800ms. Does it start a thread? I don't care. Does it create a hidden window and use system timers (ew)? I don't care. Does it hire midgets to turn my light on and off? I don't care.

And I especially don't want to have to care that if I ever create an Animator, I have to keep track of it and call a special method when my program exits, in contrast to every other object. It should be a concern of the library implementor, not the library consumer.


EDIT: The code is actually short enough to show. I'll include it for reference, sans methods that add events to the list:

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

你在看孤独的风景 2024-07-20 23:29:29

也许设置 Thread.IsBackground 属性为真?

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 =)

Maybe set the Thread.IsBackground property to true?

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

Another option is to use the Interrupt method to wake it up. Just make sure that you catch the ThreadInterruptedException in the thread that you are interrupting, and that it shuts down when it happens.

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

Not quite sure how that EventWaitHandle of yours works though... When I did something similar once, I just used the regular Thread.Sleep =)

白况 2024-07-20 23:29:29

我认为要求客户端 Stop() 关闭线程根本不是不合理的。 有多种方法可以创建线程,这些线程的继续执行不会阻止应用程序退出(尽管我不知道详细信息)。 但期望启动和终止工作线程对于客户端来说并不是太大的负担。

I don't think it is unreasonable to require clients to Stop() the thread for shutdown at all. There are ways you can create threads whose continued execution will not stop the application from exiting (although I don't have the details off the top of my head). But expecting to launch and terminate a worker thread is not too much of a burden for the client.

苹果你个爱泡泡 2024-07-20 23:29:29

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

There is no way to get .NET to notify your thread without the clients cooperation. If you're designing your library to have a long running background thread, then the client app has to be designed to know about it.

若水微香 2024-07-20 23:29:29

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

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

Application::ApplicationExit is a static event, is it acceptable to listen for it and do your special cleanup work?

Implementing IDisposable should be enough indication that your clients should be using your class in a "using" block.

桜花祭 2024-07-20 23:29:29

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

Implement IDisposable properly, including implementing a finaliser that calls Dispose(true). You Animator object can then do any clean up it wishes to, including stopping the thread if necessary.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文