Правильный способ очистить постоянный поток в C # - PullRequest
3 голосов
/ 18 февраля 2009

У меня есть объект, временная шкала, которая инкапсулирует поток. События могут быть запланированы на временной шкале; поток будет ждать, пока не наступит время выполнить какое-либо событие, выполнить его и вернуться в спящий режим (либо (а) на время, необходимое для перехода к следующему событию, либо (б) на неопределенный срок, если больше нет событий).

Спящий режим обрабатывается с помощью WaitEventHandle, который запускается при изменении списка событий (поскольку может потребоваться отрегулировать задержку ожидания) или когда поток должен быть остановлен (чтобы поток мог завершиться изящно).

Деструктор вызывает Stop (), и я даже реализовал IDisposable, а Dispose () также вызывает Stop ().

Тем не менее, когда я использую этот компонент в приложении форм, мое приложение никогда не будет корректно закрываться при закрытии формы. По какой-то причине Stop () никогда не вызывается, поэтому ни деструктор моего объекта, ни метод Dispose () не вызывают, до того, как .NET решит дождаться завершения всех потоков.

Я полагаю, что решение будет заключаться в том, чтобы явно вызвать Dispose () самостоятельно для события FormClose, но поскольку этот класс будет находиться в библиотеке, и на самом деле это уровень глубже ( разработчик приложения никогда не увидит класс Timeline), это кажется очень уродливым и лишним (ненужным) недостатком для разработчика приложения. Предложение using (), которое я обычно использую, когда освобождение ресурса становится проблемой, не применяется, поскольку это будет долгоживущий объект.

С одной стороны, я могу понять, что .NET захочет дождаться завершения всех потоков, прежде чем завершит последний раунд сборки мусора, но в этом случае возникает очень неуклюжая ситуация.

Как я могу правильно очистить свой поток, не добавляя требований к потребителям моей библиотеки? Другими словами, как я могу заставить .NET уведомлять мой объект при выходе из приложения, но до того, как он будет ожидать завершения всех потоков?


РЕДАКТИРОВАТЬ: В ответ на людей, говорящих, что это нормально для клиентской программы, чтобы быть в курсе потока: я уважительно не согласен.

Как я уже говорил в моем первоначальном посте, тема скрыта в другом объекте (аниматоре). Я создаю экземпляр 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 ]

4 голосов
/ 18 февраля 2009

Может быть установить для свойства 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 =)

3 голосов
/ 18 февраля 2009

Я не думаю, что было бы неразумно требовать от клиентов остановки () потока для завершения вообще. Существуют способы создания потоков, чье продолжение выполнения не помешает выходу приложения (хотя у меня нет подробностей в голове). Но ожидание запуска и завершения рабочего потока не является слишком обременительным для клиента.

1 голос
/ 18 февраля 2009

Application :: ApplicationExit является статическим событием, приемлемо ли его прослушивать и выполнять вашу специальную работу по очистке?

Реализация IDisposable должна быть достаточным признаком того, что ваши клиенты должны использовать ваш класс в блоке "using".

1 голос
/ 18 февраля 2009

Нет способа заставить .NET уведомлять вашу ветку без сотрудничества с клиентами. Если вы разрабатываете свою библиотеку, чтобы иметь длительный фоновый поток, то клиентское приложение должно быть разработано, чтобы об этом знать.

0 голосов
/ 18 февраля 2009

Реализуйте IDisposable правильно , включая реализацию финализатора, который вызывает Dispose (true). Ваш объект Animator может затем выполнить любую очистку, какую пожелает, включая остановку потока при необходимости.

...