C # многопоточное консольное приложение — консоль завершает работу до завершения потоков

#c# #multithreading

#c# #многопоточность

Вопрос:

У меня есть консольное приложение на c #, которое создает до 5 потоков.

Потоки выполняются нормально, но поток пользовательского интерфейса завершает свою работу.

Есть ли способ поддерживать работу основного потока пользовательского интерфейса до тех пор, пока выполняются побочные потоки?

 foreach (var url in urls)
{
    Console.WriteLine("starting thread: "   url); 
    ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(myMethod), url);
}
  

Я запускаю свои потоки в соответствии с приведенным выше кодом.

Комментарии:

1. Что, если вы создали обычные потоки вместо использования пула потоков?

2. Какая версия .NET? Если .NET 4.0, я бы рекомендовал задачи TPL, хранить все задачи в массиве и использовать задачи. Подождите (myTaskArray) перед выходом.

3. Я думаю, что потоки должны быть BackgroundWorker=false

4. @James должен был опубликовать это в качестве ответа, и отличная работа в блоге, кстати 🙂

Ответ №1:

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

У вас есть несколько вариантов:

  • подождите, например, с семафором
  • подождите на счетчике с помощью функции Sleep() , очень грубо, но подходит для простого консольного приложения.
  • используйте TPL, Parallel.ForEach(urls, url => MyMethod(url));

Комментарии:

1. 1, но я бы вообще не рекомендовал счетчик Sleep.. также стоит отметить, что можно использовать CountDownEvent .

2. @Lirik Значение обратного отсчета равно Fx4 , тогда вы также можете использовать TPL.

3. @Henk, при использовании TPL возникает определенная нагрузка, и если задачи очень короткие, то TPL не так эффективен. В таких случаях гораздо лучше использовать a ThreadPool или просто иметь несколько выделенных потоков-потребителей, которые обрабатывают задачи. Вы можете использовать a CountDownEvent в обоих случаях или просто вызвать Join потоки…

4. @lirik: OP обрабатывает URL-адреса, я уверен, что задача доступна.

Ответ №2:

Если вы используете .NET 4.0:

 var tasks = new List<Task>();

foreach(var url in urls)
{
    tasks.Add(Task.Factory.StartNew(myMethod, url));
}

// do other stuff...

// On shutdown, give yourself X number of seconds to wait for them to complete...
Task.WaitAll(tasks.ToArray(), TimeSpan.FromSeconds(30));
  

Ответ №3:

Ах — пул потоков является фоновым. Оно ставится в очередь, но затем ваша программа завершается. ЗАКОНЧЕННЫЕ. Программа завершается.

Прочитайте семафоры (WaitSignal) и подождите — потоки в обратном вызове в конце сигнализируют о завершении, когда все сигнализируют, что основной поток может продолжаться.

Ответ №4:

Если вы используете .net 4, то:

 urls.AsParallel().ForAll(MyMethod);
  

До .net 4 затем запускайте отдельные потоки, сохраняйте их в списке и вызывайте Join() . Тот факт, что рабочие элементы не являются фоновыми, сохранит их работоспособность после завершения основного потока, но Join() является более явным.

         List<Thread> workers = new List<Thread>();
        foreach(var url in urls)
        {
            Thread t = new Thread(MyMethod) {IsBackground = false};
            workers.Add(t);
            t.Start(url);
        }

        foreach (var worker in workers)
        {
            worker.Join();
        }
  

Ответ №5:

Самый простой способ исправить вашу проблему.

В вашем классе program:

 static volatile int ThreadsComplete = 0;
  

В вашем «myMethod» в конце перед возвратом:

 //ThreadsComplete  ; //*edit* for safety's sake
Interlocked.Increment(ref ThreadsComplete);
  

В вашем основном методе, прежде чем он вернется / завершится:

 while(ThreadsComplete < urls.Count) { Thread.Sleep(10); }
  

Приведенное выше по существу объединяет метод синхронизации WaitForAll.

Комментарии:

1. Вот почему у вас есть CountdownEvent … нет необходимости в циклическом или спящем режиме, что в любом случае является плохой практикой для ожидания.

2. @Хенк Холтерман и Лирик, разве вы не согласны с тем, что «плохая практика» немного более субъективна, чем «неправильный ответ». Этот ответ был указан как взлом, OP одобрил его. Итак, я не понимаю, к чему вы клоните. Кроме того, CountdownEvent является довольно новым дополнением к . Net и в этом конкретном случае было бы больше накладных расходов на программирование, чем просто использование параллельного ForEach . Итак, в ЭТОМ случае CountdownEvent не будет «лучшей практикой», верно?

Ответ №6:

в основном:

 var m = new ManualResetEvent(false);
// do something
foreach (var url in urls)
{
  Console.WriteLine("starting thread: "   url); 
  ThreadPool.QueueUserWorkItem(new System.Threading.WaitCallback(myMethod), url);
}
m.WaitOne();


private static void myMethod(object obj)
{
  try{
   // do smt
  }
  finally {
    m.Set();
  }
}
  

Комментарии:

1. Если он изменит значение ManualResetEvent на a CountDownEvent , то это будет довольно хорошим решением.

2. @Lirik С достаточным количеством изменений он также мог бы написать стихотворение

3. да, конечно, CountdownEvent — это правда. никогда не стоит недооценивать это!