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