2013-08-11 6 views
4

У меня возникли проблемы с пониманием того, почему мое консольное приложение не ждет, пока поток, который он порождает , полностью завершает работу. Я думаю, что это связано с тем, что рассматриваемая нить порождает собственные потоки детей и/или включение System.TimerКонсольное приложение не подчиняется Thread.Join

Основной поток программы выглядит следующим образом. Main создает новый поток против метода Simulator.Start и затем присоединяется, пока этот поток не завершится. Simulator.Start создает новый Timer (чтобы ограничить время его выполнения), а затем создает/запускает кучу дочерних потоков. Когда событие Elapsed возбуждается Timer, это означает, что Симулятор должен прекратить все свои дочерние потоки и сгенерировать отчет. Проблема заключается в том, что консольное приложение завершается, как только все дочерние потоки завершаются, а код для создания отчета никогда не запускается (см. Метод Simulator.Stop ниже).

Надеюсь, некоторые псевдо-код поможет:

public class Program 
{ 
    private static Simulator _simulator; 

    private static void Main(string[] args) 
    { 
     var options = new SimulationOptions(); 
     //check for valid options 
     if (!Parser.Default.ParseArguments(args, options)) return; 

     _simulator = new Simulator(options); 

     var thread = new Thread(_simulator.Start) {IsBackground = false}; 
     thread.Start(); 
     thread.Join(); 
    } 
} 

public class Simulator 
{ 
    private readonly SimulationOptions _options; 
    private readonly List<Thread> _threads = new List<Thread>(); 
    private readonly List<Worker> _workers = new List<Worker>(); 
    private static Timer _timer; 

    public Simulator(SimulationOptions options) 
    { 
     _options = options; 
     StartTimer(_options.LengthOfTest); 
    } 

    private void StartTimer(int lengthOfTest) 
    { 
     _timer = new Timer {Interval = lengthOfTest*1000}; 
     _timer.Elapsed += Timer_Elapsed; 
     _timer.Start(); 
    } 

    private void Timer_Elapsed(object sender, ElapsedEventArgs e) 
    { 
     _timer.Stop(); 
     Stop(); 
    } 

    public void Stop() 
    { 
     // Request that the worker thread stop itself: 
     foreach (Worker worker in _workers) 
     { 
      worker.RequestStop(); 
     } 

     GenerateReport(); //<-- this code never gets executed 
    } 

    private XDocument GenerateReport() 
    { 
     //build an awesome report 
    } 

    public void Start() 
    { 
     _threads.Clear(); 
     _workers.Clear(); 
     for (int i = 0; i < _options.NumberOfClients; i++) 
     { 
      _workers.Add(new Worker()); 
      _threads.Add(new Thread(_workers.Last().PumpMessages)); 
      _threads.Last().Start(); 
     } 
    } 
} 

public class Worker 
{ 
    private bool _shouldStop = false; 

    public void PumpMessages() 
    { 
     while (!_shouldStop) 
     { 
      //does cool stuff until told to stop 
     } 
    } 

    public void RequestStop() 
    { 
     _shouldStop = true; 
    } 
} 
+2

Конечно, он проходит через метод Start(), занимает не более нескольких микросекунд. Вначале просто не было смысла называть Start() для рабочего потока. Вам необходимо присоединиться() все эти потоки, которые вы запускаете. –

+0

Проблема не в методе «Пуск». Консольное приложение зависает достаточно долго для события Timer_Elapsed, которое запускает метод Stop. Однако выполнение заканчивается в середине этого метода. –

ответ

3

Ничто в вашем методе запуска не держит нить жив. Когда следующий метод заканчивается, также происходит поток. Затем вы вызываете Thread.Join, и это конец этого.

public void Start() 
{ 
    _threads.Clear(); 
    _workers.Clear(); 
    for (int i = 0; i < _options.NumberOfClients; i++) 
    { 
     _workers.Add(new Worker()); 
     _threads.Add(new Thread(_workers.Last().PumpMessages)); 
     _threads.Last().Start(); 
    } 
} 

Если вы собираетесь на ожидании этой работы, чтобы закончить, рассмотреть ожидание в ManualResetEvent для каждого рабочего потока, который вы используете.

http://msdn.microsoft.com/en-us/library/system.threading.manualresetevent.aspx http://msdn.microsoft.com/en-us/library/system.threading.waithandle.waitall.aspx

Ваш метод должен выглядеть следующим образом.

public void Start() 
{ 
    _threads.Clear(); 
    _workers.Clear(); 
    var evts = new List<ManualResetEvent>() 
    for (int i = 0; i < _options.NumberOfClients; i++) 
    { 
     ManualResetEvent evt = new ManualResetEvent(false); 
     evts.Add(evt); 
     _workers.Add(new Worker(evt)); 
     _threads.Add(new Thread(_workers.Last().PumpMessages)); 
     _threads.Last().Start(); 
    } 
    WaitHandle.WaitAll(evts.ToArray()); 
} 

public class Worker 
{ 
    private bool _shouldStop = false; 
    private readonly ManualResetEvent @event; 

    public Worker(ManualResetEvent @event) 
    { 
     [email protected] = @event; 
    } 
    public void PumpMessages() 
    { 
     while (!_shouldStop) 
     { 
      //does cool stuff until told to stop 
     } 
     @event.Set(); 
    } 
    public void RequestStop() 
    { 
     _shouldStop = true; 
    } 
} 
+0

Или еще лучше использовать задачи. –

+1

Да, или еще лучше, используйте параллельную библиотеку задач с async/await. Но если вы просто узнаете о потоках, это хорошее упражнение, чтобы пройти. –

+0

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

2

Регистрация метод ждет только нити, например, вы присоединились, так Simulator.Start просто создает некоторые темы, и она заканчивается, в результате Join возвращается и ваш основной поток завершается. Но все же ваше приложение живое (причина, по которой некоторые другие потоки Foreground по-прежнему работают).

generate a report never gets executed? Why? 

Процесс прекращается, когда все Foreground Threads завершается. так как только ваши дочерние потоки возвращаются из PumpMessages метода при вызове RequestStop в цикле, все ваши переднего плана нитей завершает

public void Stop() 
{ 
    // Request that the worker thread stop itself: 
    foreach (Worker worker in _workers) 
    { 
     worker.RequestStop(); 
    } 
    <--here all foreground threads are ready to terminate 
    GenerateReport(); //<-- this code never gets executed 
} 

Это было немного вводит в заблуждение, что я сказал, что all foreground threads die after the loop. Чтобы было ясно, предположим, что мы дали инструкции, чтобы рабочие потоки перестали работать, поэтому все потоки могут или не умереть до выполнения метода GenerateReport. да есть Race Если рабочие потоки выигрывают гонку, мы теряем ее и наоборот. иногда ваш GenerateReport может выполняться без каких-либо проблем.

Как это исправить? Мы просто ждем завершения всех наших рабочих потоков. это оно.

public void Start() 
{ 
    _threads.Clear(); 
    _workers.Clear(); 
    for (int i = 0; i < _options.NumberOfClients; i++) 
    { 
     _workers.Add(new Worker()); 
     _threads.Add(new Thread(_workers.Last().PumpMessages)); 
     _threads.Last().Start(); 
    } 
    foreach (var t in _threads) 
     t.Join(); 
} 
+0

Мне нравится ваш ответ лучше, чем у меня, но я думаю, что ваше объяснение Stop не является полным. Это не совсем то, что «все передние нити умирают». Дело в том, что существует условие гонки, потому что Stop вызывается из потока таймера, и здесь нет ничего для координации метода Stop с основным исполнением. Тем не менее, ваше решение t.Join - это путь. –

+0

@PhillipScottGivens Я собирался уходить, просто ответил в спешке, позвольте мне пересмотреть его –

+0

Я добавил утверждения соединения к методу Start, но это не имело никакого значения. Просто хотел убедиться, что вы знаете, что поток Console зависает до тех пор, пока не будет выполняться метод Stop ... но он сворачивается перед GenerateReports. –

Смежные вопросы