Как правильно закрыть приложение C #, которое создало несколько потоков? - PullRequest
14 голосов
/ 15 октября 2010

Я пишу приложение с графическим интерфейсом.

Приложение открывает несколько потоков в течение срока службы. Один из потоков обрабатывает события, которые могут поступать из других приложений, поэтому он ожидает в цикле while (true) событие, которое никогда не завершается.

Пользователь может закрыть приложение в любую минуту. Я хочу закрыть все потоки, которые открыло основное приложение.

Я использую Process.GetCurrentProcess (). Kill (); разобраться с этой проблемой на данный момент.

Это хорошее решение? Если нет, то почему и как правильно решить эту проблему, как закрыть все потоки, которые были открыты основным приложением?

Ответы [ 8 ]

23 голосов
/ 15 октября 2010

Если вы создаете новые потоки в качестве фоновых потоков (установив IsBackground перед их запуском), они автоматически остановятся, когда основной поток (поток приложения) завершится.

(из MSDN):

Поток - это фоновый поток или поток переднего плана. Фоновые потоки идентичны приоритетным, за исключением того, что фоновые потоки не препятствуют завершению процесса. Как только все потоки переднего плана, принадлежащие процессу, завершаются, среда выполнения общего языка завершает процесс. Все оставшиеся фоновые потоки останавливаются и не завершаются.

2 голосов
/ 15 октября 2010

Как только у вас уже есть потоки, ожидающие каких-либо событий, просто добавьте еще одно событие, которое при запуске вызовет завершение потока.

Если вам не нужно предоставлять какие-либо средства постепенного отключения для других потоков, вы можете переключить их в режим «фонового потока» для обеспечения автоматического завершения - см. MSDN для подробного обсуждения этой темы.

1 голос
/ 15 октября 2010

Вы должны ждать в цикле с 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();  
    }
}
1 голос
/ 15 октября 2010

Существует множество способов справиться с этим, но в идеале вы хотите, чтобы ваши потоки нормально выходили самостоятельно, а не просто убивали процесс.

Вы можете сделать что-то очень простое, как это:

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. Это очень простой пример, но он дает вам отправную точку.

0 голосов
/ 19 октября 2010

Рассмотрите возможность использования класса BackGroundWorker . Поскольку он использует пул потоков (через BeginInvoke ()), вы получите фоновые потоки. В качестве бонуса вы получаете удобные отчеты о проделанной работе, обратные вызовы отмены и завершения (уже перенаправленные в поток пользовательского интерфейса).

0 голосов
/ 16 октября 2010

Вообще, как я это делаю:

  • Создайте класс, который инкапсулирует это поведение (например, обрабатывает входящие сообщения в фоновом режиме
  • Класс должен наследоваться от IDisposable. Когда вызывается Dispose (), устанавливается закрытая переменная с именем _disposed
  • Создайте мою отдельную тему в конструкторе моего класса.
  • Иметь личное событие AutoResetEvent с именем _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's possible multiple sets of work accumulated or that the previous loop picked up the work and there's 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)
    }
  }
}
0 голосов
/ 15 октября 2010

Не теряйте свои темы вокруг приложения - держите их где-нибудь (List<Thread> подойдет). Затем, когда наступит подходящий момент (время закрытия), уведомите каждого, что он должен закончить то, что он делает, и выйти.

Затем .Join() все из них, затем разрешите приложению выйти.

Никогда не ходите в царство ThreadAbort, там скрывается темная сторона силы.

0 голосов
/ 15 октября 2010

Как вы упомянули, это приложение с графическим интерфейсом, поэтому основной поток, отвечающий за цикл обработки сообщений, отвечает за оповещение бесконечного (while(true)) цикла о том, что пользователь хочет выйти из программы.Я рекомендую заменить true другим boolean, чтобы сообщить, что пользователь закрыл окно следующим образом: while(windowIsOpen) и установить его в значение false при выгрузке вашей формы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...