(Возможно, это немного низкоуровневый, но по крайней мере это простое решение. Поскольку я не знаю API C #, это общее решение для любого языка, использующего пулы потоков.)
Вставить контрольную задачу после каждой реальной задачи, которая обновляет значение времени с текущим временем. Если это значение превышает максимальное время выполнения задачи (скажем, 10 секунд), вы знаете, что что-то застряло.
Вместо того, чтобы устанавливать время и опрашивать его, вы можете непрерывно устанавливать и сбрасывать некоторые таймеры на 10 секунд в будущем. Когда это срабатывает, задача зависла.
Лучший способ, вероятно, заключить каждую задачу в класс задач «Сторожевой», который делает это автоматически. Таким образом, по завершении вы очистите таймер и сможете установить таймаут для каждой задачи, что может быть полезно.
Вам, очевидно, нужен один объект времени / таймера для каждого потока в пуле потоков, но это можно решить с помощью локальных переменных потока.
Обратите внимание, что это решение не требует от вас изменения кода ваших задач. Он только изменяет код, помещающий задачи в пул.