Поток не просыпается из Thread.Sleep()
-
09-06-2019 - |
Вопрос
У нас есть служба Windows, написанная на C#.Служба порождает поток, который делает следующее:
private void ThreadWorkerFunction()
{
while(false == _stop) // stop flag set by other thread
{
try
{
openConnection();
doStuff();
closeConnection();
}
catch (Exception ex)
{
log.Error("Something went wrong.", ex);
Thread.Sleep(TimeSpan.FromMinutes(10));
}
}
}
Мы подключили Thread.Sleep через пару раз, когда база данных исчезла, и мы вернулись к файлам журналов размером 3 ГБ, полным ошибок подключения к базе данных.
В течение нескольких месяцев все работало нормально, но недавно мы видели несколько случаев, когда оператор log.Error() регистрирует исключение «System.InvalidOperationException:Эта SqlTransaction завершена;оно больше не может быть использовано», а затем никогда не возвращается.Службу можно оставить работающей в течение нескольких дней, но больше ничего не будет зарегистрировано.
Почитав немного, я знаю, что Thread.Sleep не идеален, но почему он просто никогда не вернется?
Решение
Покопайтесь и узнаете?Воткните этому ублюдку отладчик!
Я вижу как минимум следующие возможности:
- система журналирования зависает;
- поток завершился нормально, но служба все еще работает, поскольку в какой-то другой части возникла логическая ошибка.
И возможно, но почти наверняка нет, следующее:
- Sleep() зависает.
Но в любом случае подключение отладчика покажет, существует ли еще тред и действительно ли он завис.
Другие советы
Мы подключили Thread.Sleep через пару раз, когда база данных исчезла, и мы вернулись к файлам журналов размером 3 ГБ, полным ошибок подключения к базе данных.
Я думаю, что лучшим вариантом было бы сделать так, чтобы ваша система журналирования ловила дубликаты и могла писать что-то вроде: «Предыдущее сообщение было повторено N раз».
Предположим, я написал стандартную заметку о том, как вам следует открывать соединение в последний момент и закрывать его при первой же возможности, вместо того, чтобы охватывать потенциально огромную функцию так, как вы это сделали (но, возможно, это артефакт вашего демонстрационного кода, и ваше приложение действительно написано правильно).
Когда вы говорите, что он сообщает об ошибке, которую вы описываете, вы имеете в виду, что этот обработчик сообщает об ошибке?Причина, по которой мне это непонятно, заключается в том, что во фрагменте кода вы говорите: «Что-то пошло не так», но вы не сказали этого в своем описании;Я бы не хотел, чтобы это было настолько глупо, поскольку исключение перехватывается где-то еще, и код зависает где-то кроме сна.
У меня была точно такая же проблема.Перемещение строки Sleep за пределы обработчика исключений устранило проблему, например:
bool hadError = false;
try {
...
} catch (...) {
hadError = true;
}
if (hadError)
Thread.Sleep(...);
Прерывание потоков, похоже, не работает в контексте обработчика исключений.
Вы пробовали использовать Монитор.Пульс (перед запуском убедитесь, что ваш поток использует управление потоками), чтобы заставить поток что-то сделать?Если это сработает, вам придется немного больше изучить логику потоков.
Из опубликованного вами кода неясно, что после создания исключения система определенно сможет перезагрузиться - например.если исключение исходит из doStuff(), то поток управления вернется (после 10-минутного ожидания) в openConnection(), даже не проходя через closeConnection().
Но, как говорили другие, просто подключите отладчик и найдите, где он на самом деле находится.
Попробуйте Thread.Sleep(10 * 60 * 1000)
Я так и не понял до конца, что происходит, но, похоже, это было связано с появлением ThreadInterruptedExceptions во время 10-минутного сна, поэтому я перешел на код:
private void ThreadWorkerFunction()
{
DateTime? timeout = null;
while (!_stop)
{
try
{
if (timeout == null || timeout < DateTime.Now)
{
openDatabaseConnections();
doStuff();
closeDatabaseConnections();
}
else
{
Thread.Sleep(1000);
}
}
catch (ThreadInterruptedException tiex)
{
log.Error("The worker thread was interrupted... ignoring.", tiex);
}
catch (Exception ex)
{
log.Error("Something went wrong.", ex);
timeout = DateTime.Now + TimeSpan.FromMinutes(10);
}
}
}
Помимо специального перехвата ThreadInterruptedException, это просто кажется безопаснее, поскольку все спящие процессы происходят внутри блока try, поэтому все непредвиденные события будут регистрироваться.Я обновлю этот ответ, если когда-нибудь узнаю больше.
Наткнулся на это, когда искал собственную проблему Thread.Sleep.Это может быть связано, а может и не быть связано, но если ваш doSomething() выдает исключение, closeDatabaseConnections() не произойдет, что может привести к утечке ресурсов.Я бы поместил это в блокfinally.Просто кое что для раздумий.