#c# #multithreading
#c# #многопоточность
Вопрос:
У меня странная проблема:
В моем приложении C # я создаю другой поток, вот так:
Thread printThread = new Thread(printWorker);
printThread.Name = "Logger MainThread";
printThread.IsBackground = true;
printThread.Start();
Когда мой основной поток завершается, этот новый поток просто продолжает работать, хотя он помечен как фоновый.
Каковы могут быть причины этого? Этот объект содержит объект Mutex, не уверен, что это может быть причиной…
У кого-нибудь есть идеи?
Вот код из метода printWorker:
while (loggerIsActive)
{
LogMessage log = LoggerQueue.Dequeue();
if (log.message != null)
{
syncLogObj.WaitOne();
lock (writerobj)
{
StreamWriter sw;
if (!File.Exists(fName))
{
sw = File.CreateText(fName);
}
else
{
sw = new StreamWriter(fName, true);
}
using (sw)
{
if (log.message != "")
{
if (log.message.EndsWith("rn"))
{
log.message =
log.message.Substring(0, log.message.Length - 2);
}
sw.WriteLine(string.Format("[{0}][{3}][{1}] | {2}",
log.msgTime,
log.level.ToString(),
log.message,
log.sender.ToString()));
}
sw.Flush();
sw.Close();
}
}
syncLogObj.ReleaseMutex();
}
Thread.Sleep(5);
}
Комментарии:
1. Основываясь на том факте, что поток не прерывается операционной системой, единственное, что я мог видеть, что заставило бы этот поток что-либо делать, — это
while
цикл. Начиная с очевидного,loggerIsActive
установлено значение false?2. Можете ли вы опубликовать свой метод main ()?
3. Цикл while выполняется в другом потоке, который помечен как фоновый. Почему он остается после закрытия загружающего его приложения?
4. Вы уверены, что ваш основной поток завершен?
5. Я снова протестировал, но проблема не воспроизводится. Однако я буду следить за этим. Основной поток наверняка завершался, когда возникла эта проблема.
Ответ №1:
Попробуйте это:
Запустите приложение через VS и завершите работу в обычном режиме. VS должен оставаться в режиме отладки, как вы описали. Нажмите на кнопку паузы (прервать все), а затем перейдите в раздел Отладка-> Windows-> Потоки. Видите ли вы свой «Logger MainThread» в списке?
- Если это так, дважды щелкните по нему, это должно привести вас к строке кода, которую поток выполняет в данный момент. Шаг — отладьте оттуда и посмотрите, почему он не завершается.
- Если вы этого не видите, попробуйте посмотреть на другие потоки, которые не завершились, и попытаться найти проблему.
В противном случае, при такого рода проблемах всегда полезно отслеживать состояние программы через систему.Диагностика.Отладка.Инструкции печати (вы можете видеть, как они печатаются в окне вывода VS).
Комментарии:
1. @boris это именно то, что я уже сделал. Закройте основное приложение, VS останется в режиме отладки. CTRL-ALT-BREAK, перебрасывает меня в поток строк. Sleep (5) в коде, который я опубликовал выше. Этот поток должен был быть прерван при закрытии приложения.
2. @liortal: Преобразуйте локальную переменную
printThread
в частное поле, а затем, когда вы делаете паузу в потоке. Переход в режим ожидания (5) проверьте в немедленном окне_printThread.IsBackground;
Что это говорит? Возникает ли исключение AbandonedMutexException при закрытии основного приложения? И куда вас приводит этап отладки (F10) после строки Sleep (5)?3. @boris я сделаю это в закрытом поле. Существуют ли сценарии, в которых поток, определенный как фоновый, не будет запущен в качестве фонового потока? Также я не получаю это исключение, какой поток / AppDomain должен выдавать исключения такого типа?
4. Вы должны были упомянуть ту маленькую деталь, что поток регистратора запущен в другом домене приложения :). Другой домен приложения — это другой изолированный исполнитель. окружающая среда, и я не был бы удивлен, что закрытие приложения не приводит к уничтожению потоков из другого домена приложения. На самом деле я был бы удивлен, если бы это было так, потому что тогда это не была бы изолированная среда выполнения. И теперь я понимаю, почему вам нужен был полноценный объект Mutex вместо простой блокировки…
5. Это всего лишь предположение, я подумал об этом объяснении из-за отсутствия каких-либо других объяснений. Я не писал этот код, и я не знаю, в каких контекстах он создается. Теперь, когда мы подняли этот параметр, он соответствует всем требованиям — если какой-то код в другом домене приложения создавал этот объект и порождал новый поток, этот поток не завершится, когда основной домен приложения будет выгружен. Проверю это завтра в офисе. Если это так, мне нужно придумать решение для уничтожения этого домена приложения при закрытии приложения.
Ответ №2:
убейте его.
Не красиво. Но это не TV. Читать дальше:
1) Не уверен, что вы используете его, но, похоже, вам следует заблокировать loggerqueue перед тем, как ставить в очередь (основной pgm) или удалять из очереди (поток).
2) Нет необходимости блокировать writerobj только с помощью этой настройки. Но на самом деле вы должны, чтобы вы могли безопасно прервать поток не во время записи:
- основной поток:
- делайте все
- перед закрытием: -заблокировать writerobj -printthread.прервать
- рабочий поток:
- добавьте try catch для обработки исключения threadabort и просто завершите работу
Если вы правильно это делаете, вам не нужно использовать ожидания и мьютексы. Если вы используете wait должным образом, вам все равно не понадобится режим ожидания.
Общий совет для этого приложения: почему бы не войти в основной поток? если ваше ведение журнала настолько загружено, результаты регистрации будут довольно бесполезными.
Но бывают редкие случаи, когда это может быть неправильно. Entonces……
Общий совет, чтобы потоки хорошо справлялись с этой проблемой:
- Основная программа
- инкапсулировать ведение журнала (в частности, флаг завершения, очередь и ссылку на рабочий поток) в объекте
- Ведение журнала «глобальные снобы?» — редкий повод использовать одноэлементный шаблон.
- запустите рабочий поток в объекте logger с помощью метода
- основной поток всегда вызывает один метод в объекте logger для регистрации ошибки
- Этот метод блокирует очередь и добавляет к ней.
- Используйте Monitor / Pulse / Wait, без ожидания; полно примеров предостаточно; это стоит изучить
- поскольку только этот поток в любом случае обращается к файлу, если у вас нет нескольких процессов, вам не нужен waitone / releasemutex.
- Этот метод ведения журнала monitor.передает объект
- Это освобождает монитор рабочего потока.ожидание (которое переводит процессор в режим ожидания вместо режима ожидания)
- заблокируйте очередь, только внутри блокировки удалите объект из очереди на локальную ссылку; больше ничего.
- Выполните свой обычный код протоколирования и цикл проверки выхода. Добавить
- Ваш логический код может оставить сообщение ненаписанным, если очередь заполнена при завершении:
- измените проверку выхода, чтобы вы могли сделать это без дополнительной блокировки очереди:
- переместить объявление ссылки на объект в очереди выше while; установить для него значение nothing
- измените логику в while на ‘loggerisactive или log != null’
- когда ваш основной поток завершается, в вашем коде выхода:
- установите флаг завершения
- переведите объект, который вы используете, в режим ожидания, если он не обрабатывает очередь
- Поток завершится.
Комментарии:
1. Спасибо, я считаю, что мне нужно 2-3 тщательных прочтения вашего ответа, чтобы понять его в полной мере.
2. Его код не занят-ожидание (выполняется с потоком. Режим ожидания). Этот мьютекс, называемый syncObj, используется не для одиночного доступа, а для ожидания события. Основной поток изначально владеет мьютексом. Когда у него есть что-то для регистрации, он помещает запись в очередь, вызывает Mutex.Release, а затем Mutex. Подождите один. Это освобождение заставляет поток регистратора продвигаться вперед до тех пор, пока он не вернется к своему WaitOne, что приводит к продвижению основного потока, который с тех пор ожидает. Вероятно, этот спящий режим был остатком отладки или чем-то еще, это не влияет на код.
Ответ №3:
У вас происходит много вещей, которые вы, очевидно, не показываете…
Пример: у вас есть syncLogObj.WaitOne();
, но мы не видим, где syncLogObj
объявляется или используется в другом месте вашей программы.
Кроме того, вам это не нужно… полностью избавьтесь от syncLogObj
этой вещи (включая мусор «ReleaseMutex»)… у вас уже есть блокировка (бла) {} , и это все, что вам нужно (из того кода, который вы отобразили).
Вероятно, основной поток НЕ завершается, вероятно, из-за этого или какого-либо другого объекта, который поддерживает его открытым.
Итак, простые инструкции
- Избавьтесь от
syncLogObj
(потому что у вас уже есть «блокировка») - Убедитесь, что вы где-то установили
loggerIsActive = false
.
Редактировать: Еще больше деталей!
Из того, что я вижу — вам вообще не нужен lock (writerobj)
, потому что (я совершенно уверен), похоже, у вас есть только один поток, который записывает данные в журнал.
«Блокировка» существует только в том случае, если у вас есть два или более потоков, которые выполняют этот код (в основном).
Комментарии:
1. Я не рассматривал внимательно всю систему, построенную на этом коде регистратора, и я сам не писал этот код. Я знаю, что это довольно ужасно и имеет много избыточных блокировок. На данный момент проблема вообще больше не воспроизводится. Это, конечно, не оправдание для того, чтобы не исправлять регистратор, чтобы он был более эффективным. Но это также не помогает нам выяснить, в чем была точная исходная проблема.
2. Мьютекс (
syncLogObj
) в коде предназначен не для блокировки, а для ожидания события, смотрите Мой комментарий к ответу FastAl для объяснений. Однако этоlock(writerobj)
избыточно, потому что мьютекс уже позаботился об одиночном доступе.
Ответ №4:
Если printworker
не завершится до завершения вашего основного потока, то основной поток умрет, а ваш printworker
поток будет уничтожен операционной системой. Если вы хотите, чтобы main ожидал созданный вами поток, тогда вам следует вызвать printThread.Join()
в main . Это заставит main ждать вашего потока.
Когда основной завершится, ваша программа умрет, а ваша printThread
будет уничтожена ОС, она не будет продолжать работать.
Фоновые потоки идентичны потокам переднего плана, за одним исключением: фоновый поток не поддерживает управляемую среду выполнения запущенной. Как только все потоки переднего плана были остановлены в управляемом процессе (где exe-файл является управляемой сборкой), система останавливает все фоновые потоки и завершает работу.
Комментарии:
1. в этом проблема, он все еще запущен.
2. таким образом, согласно этой информации, он не может быть запущен после завершения основного потока
3. это факт — запуск программы из VS2008, основной поток завершается, visual Studio еще не завершается полностью. CTRL-ALT-BREAK, я вижу, что этот поток все еще выполняет какую бы работу он ни выполнял, даже если это фоновый поток. То же самое при запуске . EXE не работает под VS2008.
4. откуда вы взяли эту информацию, когда «То же самое при запуске . EXE не находится под управлением VS2008»? Как вы идентифицируете этот конкретный поток?
5. Этот ответ явно не тот, который ищет пользователь. Он уже знает это, его вопрос в том, почему этого не происходит.
Ответ №5:
У Тигра Тони правильная идея, но необходимо добавить дополнительный код, чтобы прервать поток до закрытия приложения.
printThread.Join(1000);
if(printThread!=null amp;amp; printThread.IsAlive)
printThread.Abort();
Ответ №6:
Thread.Abort();
Thread.Dispose();
Это должно сработать, если я не ошибаюсь.