Гарантированный способ отменить зависание Задача?
Мне часто приходится выполнять код в отдельном потоке, который долго работает, блокируется, нестабилен и \ или может зависать вечно. Поскольку в TPL есть множество примеров, которые приятно отменяют задачу с помощью токена отмены, я так и не нашел пример, который убивает зависшую задачу. Вероятно, ожидается, что код, который зависнет навсегда, как только вы свяжетесь с оборудованием или вызовете какой-либо сторонний код. Задание, которое зависает, не может проверить токен отмены и обречено остаться в живых навсегда. В критических приложениях я оснащаю эти задачи живыми сигналами, которые отправляются через регулярные промежутки времени. Как только зависшее задание обнаружено, оно уничтожается и запускается новый экземпляр.
В приведенном ниже коде показан пример задачи, которая вызывает долго выполняющийся метод-заполнитель SomeThirdPartyLongOperation(), который потенциально может зависнуть навсегда. Сначала StopTask() проверяет, выполняется ли еще задача, и пытается отменить ее с помощью токена отмены. Если это не работает, задача зависает и основной поток прерывается \ прерывается в старом стиле.
private Task _task;
private Thread _thread;
private CancellationTokenSource _cancellationTokenSource;
public void StartTask()
{
_cancellationTokenSource = new CancellationTokenSource();
_task = Task.Factory.StartNew(() => DoWork(_cancellationTokenSource.Token), _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
public void StopTask()
{
if (_task.Status == TaskStatus.RanToCompletion)
return;
_cancellationTokenSource.Cancel();
try
{
_task.Wait(2000); // Wait for task to end and prevent hanging by timeout.
}
catch (AggregateException aggEx)
{
List<Exception> exceptions = aggEx.InnerExceptions.Where(e => !(e is TaskCanceledException)).ToList(); // Ignore TaskCanceledException
foreach (Exception ex in exceptions)
{
// Process exception thrown by task
}
}
if (!_task.IsCompleted) // Task hangs and didn't respond to cancellation token => old school thread abort
{
_thread.Interrupt();
if (!_thread.Join(2000))
{
_thread.Abort();
}
}
_cancellationTokenSource.Dispose();
if (_task.IsCompleted)
{
_task.Dispose();
}
}
private void DoWork(CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(Thread.CurrentThread.Name)) // Set thread name for debugging
Thread.CurrentThread.Name = "DemoThread";
_thread = Thread.CurrentThread; // Save for interrupting/aborting if thread hangs
for (int i = 0; i < 10; i++)
{
cancellationToken.ThrowIfCancellationRequested();
SomeThirdPartyLongOperation(i);
}
}
Хотя я использую эту конструкцию уже несколько лет, я хочу знать, есть ли в ней потенциальные ошибки. Я никогда не видел пример задачи, которая сохраняет основной поток или дает ему имя для упрощения отладки, поэтому я немного не уверен, что это правильный путь. Комментировать любые детали можно только приветствовать!
2 ответа
Вероятно, ожидается, что код, который зависнет навсегда, как только вы свяжетесь с оборудованием или вызовете какой-либо сторонний код.
Общение: абсолютно нет. Всегда есть способ тайм-аута с API-интерфейсами связи, поэтому даже при неправильной работе оборудования нет необходимости принудительно завершать операцию ввода-вывода.
Сторонний код: только если вы параноик (или предъявляете высокие требования, такие как автоматизация 24x7).
Вот суть:
- Нет способа насильно убить задачу.
- Вы можете принудительно уничтожить поток, но это может легко вызвать серьезные проблемы с состоянием приложения, возможностью при возникновении взаимоблокировок в других частях кода и утечками ресурсов.
- Вы можете принудительно убить домен приложения, что решает большую часть проблем состояния / взаимоблокировки приложения с уничтожением потоков. Однако это не решает их все, и все еще существует проблема утечек ресурсов.
- Вы можете принудительно убить процесс. Это единственное действительно чистое и надежное решение.
Поэтому, если вы решите доверять стороннему коду, я рекомендую вам просто вызывать его, как любой другой API. Если вам требуется 100% -ная надежность независимо от сторонних библиотек, вам нужно заключить сторонний dll в отдельный процесс и использовать межпроцессное взаимодействие для его вызова.
Ваш текущий код принудительно убивает поток пула потоков, что, безусловно, не рекомендуется; эти потоки принадлежат пулу потоков, а не вам, и это все еще верно, даже если вы укажете LongRunning
, Если вы идете по маршруту kill-thread (который не совсем надежен), то я рекомендую использовать явный поток.
Вопрос в том, почему эта задача вообще висит? Я думаю, что нет универсального решения этой проблемы, но вы должны сосредоточиться на задаче, чтобы всегда быть ответственным, а не заставлять ее прерывать.
В этом коде похоже, что вы ищете простой поток, а не задачу - вы не должны связывать задачи с потоками - очень вероятно, что задача переключится на другой поток после некоторых асинхронных операций, и вы в конечном итоге убивая невинную нить, которая больше не связана с вашей задачей. Если вам действительно нужно уничтожить весь поток, создайте выделенный только для этой работы.
Вы не должны также называть или делать что-либо с любым потоком, который используется для пула задач по умолчанию. Рассмотрим этот код:
static void Main(string[] args)
{
Task.Run(sth);
Console.Read();
}
static async Task sth()
{
Thread.CurrentThread.Name = "My name";
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.Delay(1);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
Console.WriteLine(Thread.CurrentThread.Name ?? "No name");
}
выход:
3
4
No name