Как обеспечить запуск потока именно после окончания выполнения указанного числа других потоков? - PullRequest
3 голосов
/ 13 июня 2011

У меня есть класс в C #, например:

public MyClass
{
   public void Start() { ... }

   public void Method_01() { ... }
   public void Method_02() { ... }
   public void Method_03() { ... }
}

Когда я вызываю метод "Start ()", внешний класс начинает работать и создает много параллельных потоков, которые эти параллельные потоки вызывают«Method_01 ()» и «Method_02 ()» формируются над классом.после завершения работы с внешним классом метод "Method_03 ()" будет запущен в другом параллельном потоке.

Потоки "Method_01 ()" или "Method_02 ()" создаются до создания потока Method_03(), но нет гарантии завершиться до запуска потока "Method_03 ()".Я имею в виду, что «Method_01 ()» или «Method_02 ()» потеряют свой оборот ЦП, а «Method_03» получит оборот ЦП и полностью завершится.

В методе «Start ()» Iзнать общее количество потоков, которые должны создать и запустить «Method_01» и «Method_02 ()».Вопрос в том, что я ищу способ с использованием семафора или мьютекса, чтобы гарантировать, что первый оператор «Method_03 ()» будет выполнен точно после завершения всех потоков, на которых выполняются «Method_01 ()» или «Method_02 ()».

Ответы [ 5 ]

3 голосов
/ 13 июня 2011

На ум приходят три варианта:

  • Сохраните массив из Thread экземпляров и вызовите Join для всех из них Method_03.
  • Используйте один экземпляр CountdownEvent и вызовите Wait из Method_03.
  • Выделите один ManualResetEvent для каждого вызова Method_01 или Method_02 и вызовите WaitHandle.WaitAll для всех из них Method_03 (это не очень масштабируемо).

Я предпочитаю использовать CountdownEvent, потому что он намного более универсален и все еще супермасштабируем.

public class MyClass
{
  private CountdownEvent m_Finished = new CountdownEvent(0);

  public void Start()
  {
    m_Finished.AddCount(); // Increment to indicate that this thread is active.

    for (int i = 0; i < NUMBER_OF_THREADS; i++)
    {
      m_Finished.AddCount(); // Increment to indicate another active thread.
      new Thread(Method_01).Start();
    }

    for (int i = 0; i < NUMBER_OF_THREADS; i++)
    {
      m_Finished.AddCount(); // Increment to indicate another active thread.
      new Thread(Method_02).Start();
    }

    new Thread(Method_03).Start();

    m_Finished.Signal(); // Signal to indicate that this thread is done.
  }

  private void Method_01()
  {
    try
    {
      // Add your logic here.
    }
    finally
    {
      m_Finished.Signal(); // Signal to indicate that this thread is done.
    }
  }

  private void Method_02()
  {
    try
    {
      // Add your logic here.
    }
    finally
    {
      m_Finished.Signal(); // Signal to indicate that this thread is done.
    }
  }

  private void Method_03()
  {
    m_Finished.Wait(); // Wait for all signals.
    // Add your logic here.
  }
}
2 голосов
/ 13 июня 2011

Это идеальная работа для Tasks. Ниже я предполагаю, что Method01 и Method02 разрешено запускать одновременно без определенного порядка вызова или завершения (без гарантии, просто введено из памяти без тестирования):

int cTaskNumber01 = 3, cTaskNumber02 = 5;
Task tMaster = new Task(() => {
    for (int tI = 0; tI < cTaskNumber01; ++tI)
        new Task(Method01, TaskCreationOptions.AttachedToParent).Start();
    for (int tI = 0; tI < cTaskNumber02; ++tI)
        new Task(Method02, TaskCreationOptions.AttachedToParent).Start();
});
// after master and its children are finished, Method03 is invoked
tMaster.ContinueWith(Method03);
// let it go...
tMaster.Start();
1 голос
/ 13 июня 2011

На самом деле в классе Barrier есть альтернатива, появившаяся в .Net 4.0.Это упрощает способ передачи сигналов между несколькими потоками.

Вы можете сделать что-то вроде следующего кода, но это в основном полезно при синхронизации разных потоков обработки.

 public class Synchro
    {
        private Barrier _barrier;          

        public void Start(int numThreads)
        {
            _barrier = new Barrier((numThreads * 2)+1);
            for (int i = 0; i < numThreads; i++)
            {
                new Thread(Method1).Start();
                new Thread(Method2).Start(); 
            }
            new Thread(Method3).Start();
        }

        public void Method1()
        {
            //Do some work
            _barrier.SignalAndWait();
        }

        public void Method2()
        {
            //Do some other work.
            _barrier.SignalAndWait();
        }

        public void Method3()
        {
            _barrier.SignalAndWait();               
            //Do some other cleanup work.
        }
    }

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

 public class Synchro
    {
        public void Start(List<someClass> collection)
        {
            new Thread(()=>Method3(collection));
        }

        public void Method1(someClass)
        {
            //Do some work.               
        }

        public void Method2(someClass)
        {
            //Do some other work.                
        }

        public void Method3(List<someClass> collection)
        {
            //Do your work on each item in Parrallel threads.
            Parallel.ForEach(collection, x => { Method1(x); Method2(x); });
            //Do some work on the total collection like sorting or whatever.                
        }
    }
1 голос
/ 13 июня 2011

Похоже, что вам нужно сделать, это создать ManualResetEvent (инициализированный как unset) или какой-то другой WatHandle для каждого из Method_01 и Method_02, а затем заставить поток Method_03 использовать WaitHandle.WaitAll на множестве ручек.

В качестве альтернативы, если вы можете ссылаться на переменные Thread, используемые для запуска Method_01 и Method_02, вы можете использовать поток Method_03, использующий Thread.Join для ожидания обоих. Это предполагает, однако, что эти потоки фактически завершаются, когда они завершают выполнение Method_01 и Method_02 - если нет, вам необходимо прибегнуть к первому решению, которое я упомянул.

1 голос
/ 13 июня 2011

Почему бы не использовать статическую переменную static volatile int threadRuns, которая инициализируется числом потоков, которые будут запускать Method_01 и Method_02. Затем вы модифицируете каждый из этих двух методов, чтобы уменьшить threadRuns непосредственно перед выходом:

...
lock(typeof(MyClass)) {
    --threadRuns;
}
...

Затем в начале Method_03 вы ждете, пока threadRuns станет равным 0, а затем продолжаете:

while(threadRuns != 0)
   Thread.Sleep(10);

Правильно ли я понял вопрос?

...