Определить, когда все потоки закончили с#

Я очень новичок в потоковой обработке, как новичок в C#. У меня есть программа, которая будет запускать несколько потоков внутри приложения windows. Моя цель-запустить новый поток для каждого элемента в списке. Элементы в этом списке имена рабочих станций на сеть. Каждый созданный поток будет искать ремонт на каждой машине, когда поток закончит, он запишет в файл журнала любые найденные ошибки и т. д. Но я хочу иметь возможность определить, когда все потоки закончатся. Так что если я 100 машин, 100 потоков, как определить, когда все они закрыты?

вот мой метод ниже : -

private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e)
{
    if (machineList.Count() != 0)
    {
        foreach (string ws in machineList)
        {
            new Thread(new ParameterizedThreadStart(fixClient), stack).Start(ws);
        }
    }
    else
    {
         MessageBox.Show("Please import data before attempting this procedure");
    }
}

8 ответов


способ сделать это - сохранить ссылку на все потоки, а затем вступить на них. Это в основном означает, что текущий поток будет блокировать до завершения объединенного потока.

измените цикл на что-то вроде:

foreach (string ws in machineList)
{
   var thread = new Thread(new ParameterizedThreadStart(fixClient), stack);
   _machineThreads.Add(thread)
   thread.Start();
}

(где _machineThreads-это список System.Thread)

затем вы можете заблокировать, пока все не будут завершены чем-то вроде:

private void WaitUntilAllThreadsComplete()
{
   foreach (Thread machineThread in _machineThreads)
   {
      machineThread.Join();
   } 
}

- вы почти наверняка не хотите сделать это для сценария, который вы описываете:

  • вы не должны создавать большое количество потоков-явно создание сотен потоков не является отличной идеей
  • вы должны предпочесть другие подходы - попробуйте посмотреть на параллельный.По каждому элементу и

вы можете использовать: IsAlive. Но у вас есть ссылка, как

 Thread t = new Thread(new ThreadStart(...));
 t.start();
 if(t.IsAlive)
 {
    //do something
 }
 else
 {
    //do something else
 }

другой идеей было бы использовать Parallel.ForEach в отдельной нити: Это также безопасно, если у вас слишком много машин, чтобы исправить

private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e)
{

    if (machineList.Count() != 0)
    {
        AllFinished=False;
        new Thread(new ThreadStart(fixAllClients).Start();
    }
    else
    {
         MessageBox.Show("Please import data before attempting this procedure");
    }
}

private void fixAllClients(){
    var options = new ParallelOptions{MaxDegreeOfParallelism=10};
    Parallel.ForEach(machineList. options, fixClient);
    AllFinished=True;
}

никогда не ждите завершения потока или чего-либо еще в обработчике событий GUI. Если вы создаете много потоков (и да, не делайте этого - см. сообщение роба) или отправляете много задач в threadpool, последнее entitiy для завершения выполнения должно сигнализировать потоку GUI, что задание завершено. Обычно это включает вызов некоторого объекта, который отсчитывает оставшиеся задачи / потоки и сигналы, когда последний срабатывает. Посмотрите на систему.Нарезка резьбы.CountdownEvent.


есть альтернативный способ сделать это, который использует класс CountdownEvent.

код, который запускает потоки, должен увеличить счетчик и передать объект CountdownEvent каждому потоку. Каждый поток будет вызывать CountdownEvent.Сигнал (), когда он будет завершен.

следующий код иллюстрирует этот подход:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication6
{
    class Program
    {
        static void Main(string[] args)
        {
            int numTasks = 20;
            var rng = new Random();

            using (var finishedSignal = new CountdownEvent(1))
            {
                for (int i = 0; i < numTasks; ++i)
                {
                    finishedSignal.AddCount();
                    Task.Factory.StartNew(() => task(rng.Next(2000, 5000), finishedSignal));
                }

                // We started with a count of 1 to prevent a race condition.
                // Now we must decrement that count away by calling .Signal().

                finishedSignal.Signal(); 
                Console.WriteLine("Waiting for all tasks to complete...");
                finishedSignal.Wait();
            }

            Console.WriteLine("Finished waiting for all tasks to complete.");
        }

        static void task(int sleepTime, CountdownEvent finishedSignal)
        {
            Console.WriteLine("Task sleeping for " + sleepTime);
            Thread.Sleep(sleepTime);
            finishedSignal.Signal();
        }
    }
}

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

@Brian Gideon: ваше решение было бы идеальным, за исключением следующего код:

// Define a continuation that happens after everything is done.
parent.ContinueWith(
  () =>
  {
    // Code here will execute after the parent task has finished.
    // You can safely update UI controls here.
  }, TaskScheduler.FromCurrentSynchronizationContext);

конкретная проблема с этим находится в части ()=>. Это создает синтаксическую ошибку, которая читает Система Делегата.система действий.Нарезка резьбы.Задачи.Задача" не принимает 0 аргументов

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


давайте сначала что-нибудь уберем с дороги.

  • не создавайте для этого отдельные потоки. Потоки-дорогостоящий ресурс. Вместо этого используйте методы объединения потоков.
  • не блокируйте поток пользовательского интерфейса, вызывая Thread.Join, WaitHandle.WaitOne, или любой другой механизм блокирования.

вот как я бы сделал это с TPL.

private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e) 
{ 
  if (machineList.Count() != 0) 
  { 
    // Start the parent task.
    var task = Task.Factory.StartNew(
      () =>
      {
        foreach (string ws in machineList)
        {
          string capture = ws;
          // Start a new child task and attach it to the parent.
          Task.Factory.StartNew(
            () =>
            {
              fixClient(capture);
            }, TaskCreationOptions.AttachedToParent);
        }
      }, TaskCreationOptions.LongRunning);

    // Define a continuation that happens after everything is done.
    task.ContinueWith(
      (parent) =>
      {
        // Code here will execute after the parent task including its children have finished.
        // You can safely update UI controls here.
      }, TaskScheduler.FromCurrentSynchronizationContext);
  } 
  else 
  { 
    MessageBox.Show("Please import data before attempting this procedure"); 
  } 
} 

то, что я делаю здесь, создает родительскую задачу, которая сама будет создавать дочерние задачи. Уведомление что я использую TaskCreationOptions.AttachedToParent чтобы связать дочерние задачи с их родителем. Затем по родительской задаче вызываю ContinueWith который выполняется после того, как родитель и все его дети завершили. Я использую TaskScheduler.FromCurrentSynchronizationContext чтобы получить продолжение в потоке пользовательского интерфейса.

и вот альтернативное решение с использованием Parallel.ForEach. Обратите внимание, что это немного более чистое решение.

private void repairClientsToolStripMenuItem_Click(object sender, EventArgs e) 
{ 
  if (machineList.Count() != 0) 
  { 
    // Start the parent task.
    var task = Task.Factory.StartNew(
      () =>
      {
        Parallel.Foreach(machineList,
          ws =>
          {
            fixClient(ws);
          });
      }, TaskCreationOptions.LongRunning);

    // Define a continuation that happens after everything is done.
    task.ContinueWith(
      (parent) =>
      {
        // Code here will execute after the parent task has finished.
        // You can safely update UI controls here.
      }, TaskScheduler.FromCurrentSynchronizationContext);
  } 
  else 
  { 
    MessageBox.Show("Please import data before attempting this procedure"); 
  } 
} 

вы могли бы создать WaitHandle для каждого потока, который вы ждете:

WaitHandle[] waitHandles = new WaitHandle[machineList.Count()];

добавить ManualResetEvent в список и передать его в поток:

for (int i = 0; i < machineList.Count(); i++)
{
    waitHandles[i] = new ManualResetEvent(false);
    object[] parameters = new object[] { machineList[i], waitHandles[i] };
    new Thread(new ParameterizedThreadStart(fixClient), stack).Start(parameters);
}

// wait until each thread will set its manual reset event to signaled state
EventWaitHandle.WaitAll(waitHandles);

внутри вас метод потока:

public void fixClient(object state)
{
    object[] parameters = (object[])state;
    string ws = (string)parameters[0];
    EventWaitHandle waitHandle = (EventWaitHandle)parameters[1];

    // do your job 

    waitHandle.Set();
}

основной поток продолжит выполнение, когда все потоки будут ждать ручек.