C#: ожидание завершения всех потоков

Я сталкиваюсь с общим шаблоном в коде, который я пишу, где мне нужно дождаться завершения всех потоков в группе с тайм-аутом. Предполагается, что тайм-аут - это время, необходимое для завершения потоков все, поэтому простое выполнение thread.Join (timeout) для каждого потока не сработает, поскольку в этом случае возможный тайм-аут равен timeout * numThreads.

Сейчас я делаю примерно следующее:

var threadFinishEvents = new List<EventWaitHandle>();

foreach (DataObject data in dataList)
{
    // Create local variables for the thread delegate
    var threadFinish = new EventWaitHandle(false, EventResetMode.ManualReset);
    threadFinishEvents.Add(threadFinish);

    var localData = (DataObject) data.Clone();
    var thread = new Thread(
        delegate()
        {
            DoThreadStuff(localData);
            threadFinish.Set();
        }
    );
    thread.Start();
}

Mutex.WaitAll(threadFinishEvents.ToArray(), timeout);

Однако похоже, что для такого рода вещей должна быть более простая идиома.

какое-либо окончательное решение с полным исходным кодом работает? может быть, более сложный образец для уведомления об ошибках в каждом потоке и после того, как WaitAll покажет сводку?

Kiquenet 01.01.2014 14:11

Я бы поступил именно так - не могу придумать более простого способа сделать это.

Guy 04.11.2008 22:40
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
67
2
108 173
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Вне всяких сомнений, почему бы вам просто не использовать Thread.Join (тайм-аут) и убрать время, необходимое для присоединения, из общего тайм-аута?

// pseudo-c#:

TimeSpan timeout = timeoutPerThread * threads.Count();

foreach (Thread thread in threads)
{
    DateTime start = DateTime.Now;

    if (!thread.Join(timeout))
        throw new TimeoutException();

    timeout -= (DateTime.Now - start);
}

Код Редактировать: стал меньше псевдо. Не понимаю, зачем вам модифицировать ответ -2, когда ответ, который вы модифицировали +4, точно такой же, только менее подробный.

Это неверно: задан полный тайм-аут для всех потоков, любой любой поток (включая первый ожидаемый) может использовать полный тайм-аут. Ваш код не проверяет результат соединения.

Martin v. Löwis 04.11.2008 22:49

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

Omer van Kloeten 04.11.2008 23:44
Ответ принят как подходящий

Я все еще думаю, что использовать Join проще. Запишите ожидаемое время завершения (как сейчас + тайм-аут), затем в цикле выполните

if (!thread.Join(End-now))
    throw new NotFinishedInTime();

Возможно, это не вариант для вас, но если вы можете использовать параллельное расширение для .NET, тогда вы можете использовать Task вместо сырых потоков, а затем использовать Task.WaitAll(), чтобы дождаться их завершения.

Я пытался понять, как это сделать, но не смог получить никаких ответов от Google. Я знаю, что это старый поток, но вот мое решение:

Используйте следующий класс:

class ThreadWaiter
    {
        private int _numThreads = 0;
        private int _spinTime;

        public ThreadWaiter(int SpinTime)
        {
            this._spinTime = SpinTime;
        }

        public void AddThreads(int numThreads)
        {
            _numThreads += numThreads;
        }

        public void RemoveThread()
        {
            if (_numThreads > 0)
            {
                _numThreads--;
            }
        }

        public void Wait()
        {
            while (_numThreads != 0)
            {
                System.Threading.Thread.Sleep(_spinTime);
            }
        }
    }
  1. Вызовите Addthreads (int numThreads) перед выполнением потока (ов).
  2. Вызывайте RemoveThread () после завершения каждого из них.
  3. Используйте Wait () в момент, когда вы хотите дождаться завершения всех потоков прежде чем продолжить

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

xbonez 01.10.2010 18:56

Поскольку вопрос был поднят, я опубликую свое решение.

using (var finished = new CountdownEvent(1)) 
{ 
  for (DataObject data in dataList) 
  {   
    finished.AddCount();
    var localData = (DataObject)data.Clone(); 
    var thread = new Thread( 
        delegate() 
        {
          try
          {
            DoThreadStuff(localData); 
            threadFinish.Set();
          }
          finally
          {
            finished.Signal();
          }
        } 
    ); 
    thread.Start(); 
  }  
  finished.Signal(); 
  finished.Wait(YOUR_TIMEOUT); 
} 

С .NET 4.0 мне стало намного проще работать с System.Threading.Tasks. Вот цикл спин-ожидания, который у меня надежно работает. Он блокирует основной поток до завершения всех задач. Еще есть Task.WaitAll, но у меня это не всегда срабатывало.

        for (int i = 0; i < N; i++)
        {
            tasks[i] = Task.Factory.StartNew(() =>
            {               
                 DoThreadStuff(localData);
            });
        }
        while (tasks.Any(t => !t.IsCompleted)) { } //spin wait

Вы хотите Task.WaitAll(tasks) вместо этого вращения.

ladenedge 26.12.2011 21:27

@ T.Webster - что вы имеете в виду, говоря, что Task.WaitAll не всегда работал у вас? Вы предполагаете, что в этом методе есть ошибки? Вы можете привести примеры. У меня это всегда срабатывает. С другой стороны, предлагаемое вами вращение - занятие довольно дорогое. Вы забиваете свой процессор. Task.WaitAll - намного лучшее решение.

Alex Aza 03.12.2012 12:43

Если вы используете эту опцию, я бы посоветовал хотя бы поместить Thread.Yield() в тело while.

Wouter Simons 06.02.2013 16:55

какой-либо более сложный образец для уведомления об ошибках в каждом потоке и после того, как WaitAll покажет сводку?

Kiquenet 01.01.2014 14:12

Извините, -1 за spinwait. Мотивация «Task.WaitAll не всегда срабатывала для меня» без более подробной информации не может служить оправданием для этого.

erikkallen 21.04.2015 10:39

Я испытал, что WaitAll не всегда работает правильно в 4.0. Может, проблема в самих задачах. Обычно я хочу, чтобы результат был записан в задаче, и убедился, что довольно часто одна задача никогда не заканчивается записанным результатом (последним в задаче). Это либо все, либо на одну меньше. Не пропали ни два, ни три, ни N. Всегда только один, когда это происходит. Я попробовал другой вариант вращения, чтобы проверить, соответствует ли количество результатов количеству задач, и он так и не вышел из этого цикла. Задача просто исчезла до завершения, хотя WaitAll был выпущен.

Quintium 23.04.2015 17:16

@erikkallen приветствует конструктивную критику +1. Какие еще подробности вы просите меня добавить? Какое решение вы бы использовали для решения этой проблемы?

T. Webster 19.05.2015 06:46

@AlexAza, извините, я не просматривал комментарии с тех пор, как вы задали свой вопрос. Это был 2011 год, когда я написал этот ответ, и, используя Visual Studio 2015 .NET 4.0, я постараюсь очень скоро отредактировать свой ответ на примере, воспроизводящем то, что я имел в виду, говоря, что «Task.WaitAll» не всегда работает.

T. Webster 19.05.2015 07:54

@Quintium, и как у вас работал цикл spinwait моего ответа - был ли он надежным или у вас возникли проблемы?

T. Webster 19.05.2015 07:56

@ T.Webster Чтобы найти удовлетворительный ответ, мне нужна информация о том, когда встроенный метод Task.WaitAll не работает. Скорее всего, этот метод не работает, но вы что-то делаете не так.

erikkallen 19.05.2015 11:39

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

Top Cat 26.02.2018 20:05

Возможное решение:

var tasks = dataList
    .Select(data => Task.Factory.StartNew(arg => DoThreadStuff(data), TaskContinuationOptions.LongRunning | TaskContinuationOptions.PreferFairness))
    .ToArray();

var timeout = TimeSpan.FromMinutes(1);
Task.WaitAll(tasks, timeout);

Предполагая, что dataList - это список элементов, и каждый элемент должен обрабатываться в отдельном потоке.

какой-либо более сложный образец для уведомления об ошибках в каждом потоке и после того, как WaitAll покажет сводку?

Kiquenet 01.01.2014 14:11

Я прочитал книгу C# 4.0: The Complete Reference of Herbert Schildt. Автор использует соединение, чтобы дать решение:

class MyThread
    {
        public int Count;
        public Thread Thrd;
        public MyThread(string name)
        {
            Count = 0;
            Thrd = new Thread(this.Run);
            Thrd.Name = name;
            Thrd.Start();
        }
        // Entry point of thread.
        void Run()
        {
            Console.WriteLine(Thrd.Name + " starting.");
            do
            {
                Thread.Sleep(500);
                Console.WriteLine("In " + Thrd.Name +
                ", Count is " + Count);
                Count++;
            } while (Count < 10);
            Console.WriteLine(Thrd.Name + " terminating.");
        }
    }
    // Use Join() to wait for threads to end.
    class JoinThreads
    {
        static void Main()
        {
            Console.WriteLine("Main thread starting.");
            // Construct three threads.
            MyThread mt1 = new MyThread("Child #1");
            MyThread mt2 = new MyThread("Child #2");
            MyThread mt3 = new MyThread("Child #3");
            mt1.Thrd.Join();
            Console.WriteLine("Child #1 joined.");
            mt2.Thrd.Join();
            Console.WriteLine("Child #2 joined.");
            mt3.Thrd.Join();
            Console.WriteLine("Child #3 joined.");
            Console.WriteLine("Main thread ending.");
            Console.ReadKey();
        }
    }

Это не отвечает на вопрос (без тайм-аута), но я сделал очень простой метод расширения для ожидания всех потоков коллекции:

using System.Collections.Generic;
using System.Threading;
namespace Extensions
{
    public static class ThreadExtension
    {
        public static void WaitAll(this IEnumerable<Thread> threads)
        {
            if (threads!=null)
            {
                foreach(Thread thread in threads)
                { thread.Join(); }
            }
        }
    }
}

Тогда вы просто звоните:

List<Thread> threads=new List<Thread>();
//Add your threads to this collection
threads.WaitAll();

Другие вопросы по теме