views:

1232

answers:

5

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();
    }
}
+3  A: 

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.

Jeff Kotula
+1 - at the point you've explicitly set up your own Thread object, you (and whatever calls your library) is responsible for telling it to stop, not .NET
Matt Jordan
+1  A: 

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.

Costa Rica Dev
+3  A: 

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

Svish
Settings IsBackground to true suits my purposes perfectly. The Interrupt method doesn't work, of course I can shut down my thread, but there is no event on which I know when to trigger the shutdown.
rix0rrr
I don't know if it is good programming practice, but you could maybe do it in a finalizer? Anyways, not a problem if IsBackground works in your case =)
Svish
+1  A: 

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.

slf
A: 

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.

Neil Barnwell