Посмотрите ManualResetEvent, так как он сделан именно для того, что вы просите.
Ваши потоки создают новое событие сброса и добавляют его в доступную очередь, которую ваш основной поток может использовать, чтобы увидеть, работают ли еще какие-либо потоки.
// main thread owns this
private List<ManualResetEvent> _resetEvents;
...
// main thread does this to wait for executing threads to finish
WaitHandle.WaitAll(_resetEvents.ToArray(), 2000, false)
...
// worker threads do this to signal the thread is done
myResetEvent.Set();
Я могу дать вам больше примеров кода, если вы хотите, но я просто скопировал его из пары статей, которые я прочитал, когда мне пришлось делать это год назад или около того.
Забыл упомянуть, вы не можете добавить эту функцию в потоки по умолчанию, которые вы получите, когда ваш таймер срабатывает. Поэтому вы должны сделать ваш обработчик таймера очень бережливым и не делать ничего, кроме как подготовить и запустить новый рабочий поток.
...
ThreadPool.QueueUserWorkItem(new WaitCallback(MyWorkerDelegate),
myCustomObjectThatContainsAResetEvent);