Как правильно закрыть приложение С#, создавшее несколько потоков?
Я пишу графическое приложение.
Приложение открывает несколько потоков во время его жизни. Один из потоков - это обработка событий, которые могут поступать из других приложений, поэтому он ждет некоторое время (истинный) цикл для события, которое никогда не прерывалось.
Пользователь может закрыть приложение в любую минуту. Я хочу закрыть все потоки, открытые основным приложением.
Я использую Process.GetCurrentProcess(). Kill(); для решения этой проблемы на данный момент.
Это хорошее решение? Если нет, то почему и как правильно решить эту проблему, как закрыть все потоки, открытые основным приложением?
Ответы
Ответ 1
Если вы создаете новые потоки в качестве фоновых потоков (установив IsBackground перед их запуском), они автоматически остановятся, когда основной поток (поток приложения) завершается.
(из MSDN):
Нить представляет собой либо фоновый поток, либо поток переднего плана. Фоновые потоки идентичны потокам переднего плана, за исключением того, что фоновые потоки не препятствуют завершению процесса. Как только все потоки переднего плана, принадлежащие процессу, завершатся, общая среда выполнения языка завершает процесс. Любые оставшиеся фоновые потоки останавливаются и не завершаются.
Ответ 2
Как только у вас уже есть потоки, ожидающие некоторых событий, просто добавьте еще одно событие, которое при запуске проинструктирует поток прекратить.
Если вам не нужно предоставлять некоторые средства изящного отключения для других потоков, вы можете переключить их в режим "фоновый поток", чтобы обеспечить автоматическое завершение - см. MSDN для подробного обсуждения этой темы.
Ответ 3
Есть много способов справиться с этим, но в идеале вы хотите, чтобы ваши потоки нормально выходили самостоятельно, а не просто убивали процесс.
Вы можете сделать что-то очень простое:
public class ThreadSignal
{
public bool Stop { get; set; }
}
Затем в вашем поточном цикле выполните:
public void DoWork(object state)
{
ThreadSignal signal = (ThreadSignal)state;
while(!signal.Stop)
{
// Do work here
}
}
Затем, когда вы готовы остановиться, установите ThreadSignal.Stop
в true
. Это очень простой пример, но он дает вам отправную точку.
Ответ 4
Вы должны ждать в цикле с помощью ManualResetEvent (или AutoResetEvent).
Затем просто установите переменную-член в true при выключении:
public class MyForm : Form
{
private AutoResetEvent _workTrigger = new AutoResetEvent();
private bool _shuttingDown = false;
private Thread _thread;
public void Form_Initialize()
{
_thread = new Thread(MyThreadMethod);
_thread.Start();
}
public static void MyThreadMethod(object State)
{
while (!_shuttingDown)
{
//wait for jobs.
_workTrigger.WaitOne(); //can add a timeout as parameter.
//do some work here
}
}
public void Form_Closing(object source, EventArgs e)
{
_shuttingDown = true;
_workTrigger.Set();
//wait for it to exit. You could use the timeout
//parameter and a loop to not block the UI
_thread.Join();
}
}
Ответ 5
Как вы упомянули это приложение GUI, поэтому основной поток, отвечающий за цикл сообщений, отвечает за предупреждение бесконечного цикла (while(true)
), который пользователь хочет выйти из программы. Я рекомендую заменить true
на другой boolean
для сигнализации о том, что пользователь закрыл окно следующим образом: while(windowIsOpen)
и установите для него значение false при выгрузке вашей формы.
Ответ 6
Не теряйте свои потоки вокруг приложения - keep'em где-нибудь (List<Thread>
будет делать все). Затем, когда время будет правильным (время закрытия), сообщите каждому, что он должен закончить то, что он делает, и выйти.
Затем, .Join()
все из них, затем разрешите приложению выйти.
Никогда не переходите в сферу "ThreadAbort", это темная сторона силы, которая скрывается там.
Ответ 7
Как правило, я это делаю:
- Создайте класс, который инкапсулирует это поведение (например, обрабатывает входящие сообщения в фоновом режиме
- Наследовать класс от IDisposable. Когда вызывается Dispose(), задайте приватную переменную с именем _disposed
- Создайте выделенный поток в моем конструкторе класса.
- У вас есть собственный авторешетка с именем _workToDo. Ваш фоновый поток будет ждать этого события и будет выполнять только рабочий цикл, когда это событие будет сигнализировано.
- Имейте общедоступный метод отправки сообщения своему фоновому работнику, который ставит очередь на работу, а затем устанавливает _workToDo, чтобы сообщить фоновому потоку выполнить работу.
Объединяя все это, вы получаете:
public class BackgroundProcessor : IDisposed
{
private Thread _backgroundThread;
private bool _disposed;
private AutoResetEvent _workToDo = new AutoResetEvent(false);
// where T is a class with the set of parameters for your background work
private Queue<T> _workQueue = Queue.Synchronized(new Queue<T>);
public BackgroundProcessor()
{
_backgroundThread = new Thread(DoBackgroundWork);
_backgroundThread.Start();
}
public void Dispose()
{
_disposed = true;
// Wait 5 seconds for the processing of any previously submitted work to finish.
// This gives you a clean exit. May want to check return value for timeout and log
// a warning if pending background work was not completed in time.
// If you're not sure what you want to do yet, a Debug.Assert is a great place to
// start because it will let you know if you do or don't go over time in general
// in your debug builds.
// Do *not* Join() and wait infinitely. This is a great way to introduce shutdown
// hangs into your app where your UI disappears but your process hangs around
// invisibly forever. Nasty problem to debug later...
Debug.Assert(_backgroundThread.Join(5000));
}
// Called by your 'other application'
public void GiveMeWorkToDo(T workParameters)
{
_workQueue.Enqueue(workParameters);
_workToDo.Set();
}
private void DoBackgroundWork()
{
while (!_disposed)
{
// 500 ms timeout to WaitOne allows your Dispose event to be detected if there is
// No work being submitted. This is a fancier version of a Thread.Sleep(500)
// loop. This is better because you will immediately start work when a new
// message is posted instead of waiting for the current Sleep statement to time
// out first.
_workToDo.WaitOne(500);
// It possible multiple sets of work accumulated or that the previous loop picked up the work and there none left. This is a thread safe way of handling this.
T workParamters = _workQueue.Count > 0 ? workParameters = _workQueue.Dequeue() : null;
do
{
DoSomething(workParameters);
workParameters = _workQueue.Count > 0 ? workParameters = _workQueue.Dequeue() : null;
} while (workParameters != null)
}
}
}
Ответ 8
Рассмотрим использование класса BackGroundWorker. Поскольку он использует threadpool (через BeginInvoke()), вы получите фоновый поток. В качестве бонуса вы получаете удобные отчеты о ходе выполнения, аннулировании и завершении (уже настроены на поток пользовательского интерфейса).