2013-07-03 2 views
0

В настоящее время я вижу некоторые действия, когда я работаю над многопоточным сервисом Windows. Проблема, с которой я сталкиваюсь, заключается в том, что некоторые объекты, как представляется, сбрасываются при доступе из разных потоков.Неожиданное поведение при обращении к объектам из разных потоков в C#

Позвольте мне продемонстрировать, с какой-то код (упрощенно объяснить проблему) ....

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

public class ContainerService : ServiceBase 
{  
    private IEnumerable<IRunnableBatch> _services; 

    public void start() 
    { 
     _services = ServiceContainer.SvcContainer.Kernel.GetAll<IRunnableBatch>(); 
     foreach (IRunnableBatch s in _services) 
     { 
      s.run(); 
     } 
    } 

    public void stop() 
    { 
     foreach (IRunnableBatch s in _services) 
     { 
      s.stop(); 
     } 
    } 
} 

Теперь в методе Run() из класса IRunnableBatch у меня есть что-то вроде этого:

public class Batch : IRunnableBatch 
{ 
    //this class is used for starting and stopping threads as well as tracking 
    //threads to restart them should the stop 
    protected IWatchdog _watchdog; 

    ... code ommitted for brevity but the watchdog class is injected by Ninject 
     in the constructor ... 

    public void run() 
    { 
     _watchdog.startThreads(this); 
    } 

    public void stop() 
    { 
     _watchdog.stopThreads(); 
    } 
} 

А вот код Ф.О. г класс Watchdog:

public class Watchdog : IWatchdog 
{ 

    private ILog _logger; 
    private Dictionary<int, MethodInfo> _batches = new Dictionary<int, MethodInfo>(); 
    private Dictionary<int, Thread> _threads = new Dictionary<int, Thread>(); 
    private IRunnableBatch _service; 
    private Thread _watcher; 
    private Dictionary<int, ThreadFailure> _failureCounts = new Dictionary<int, ThreadFailure>(); 
    private bool _runWatchdog = true; 

    #region IWatchdog Members 

    /** 
    * This function will scan an IRunnableService for the custom attribute 
    * "BatchAttribute" and use that to determine what methods to run when 
    * a batch needs to be launched 
    */ 
    public void startThreads(IRunnableBatch s) 
    { 
     _service = s; 

     //scan service for runnable methods 
     Type t = s.GetType(); 
     MethodInfo[] methods = t.GetMethods(); 
     foreach (MethodInfo m in methods) 
     { 
      object[] attrs = m.GetCustomAttributes(typeof(BatchAttribute), true); 
      if (attrs != null && attrs.Length >= 1) 
      { 
       BatchAttribute b = attrs[0] as BatchAttribute; 
       _batches.Add(b.Batch_Number, m); 
      } 
     } 

     //loop through and see if the batches need to run 
     foreach (KeyValuePair<int, MethodInfo> kvp in _batches) 
     { 
      startThread(kvp.Key, kvp.Value); 
     } 

     //check if the watcher thread is running. If not, start it 
     if (_watcher == null || !_watcher.IsAlive) 
     { 
      _watcher = new Thread(new ThreadStart(watch)); 
      _watcher.Start(); 
      _logger.Info("Watcher thread started."); 
     } 
    } 

    private void startThread(int key, MethodInfo method) 
    { 
     if (_service.shouldBatchRun(key)) 
     { 
      Thread thread = new Thread(new ThreadStart(() => method.Invoke(_service, null))); 
      try 
      { 
       thread.Start(); 
       _logger.Info("Batch " + key + " (" + method.Name + ") has been started."); 
       if (_threads.ContainsKey(key)) 
       { 
        _threads[key] = thread; 
       } 
       else 
       { 
        _threads.Add(key, thread); 
       } 
      } 
      catch (Exception ex) 
      { 
       //mark this as the first problem starting the thread. 
       if (ex is System.Threading.ThreadStateException || ex is System.OutOfMemoryException) 
       { 
        _logger.Warn("Unable to start thread: " + method.Name, ex); 
        ThreadFailure tf = new ThreadFailure(); 
        tf.Count = 1; 
        _failureCounts.Add(key, tf); 
       } 
       else { throw; } 
      } 
     } 
    } 

    public void stopThreads() 
    { 
     _logger.Info("stopThreads called"); 
     //stop the watcher thread first 
     if (_watcher != null && _watcher.IsAlive) 
     { 
      _logger.Info("Stopping watcher thread."); 
      _runWatchdog = false; 
      _watcher.Join(); 
      _logger.Info("Watcher thread stopped."); 
     } 

     int stoppedCount = 0; 

     _logger.Info("There are " + _threads.Count + " batches to stop."); 

     while (stoppedCount < _threads.Count) 
     { 
      ArrayList stopped = new ArrayList(); 
      foreach (KeyValuePair<int, Thread> kvp in _threads) 
      { 
       if (kvp.Value.IsAlive) 
       { 
        _service.stopBatch(kvp.Key); 
        kvp.Value.Join(); //wait for thread to terminate 
        _logger.Info("Batch " + kvp.Key.ToString() + " stopped"); 
       } 
       else 
       { 
        _logger.Info("Batch " + kvp.Key + " (" + _batches[kvp.Key].Name + ") has been stopped"); 
        stoppedCount++; 
        stopped.Add(kvp.Key); 
       } 
      } 

      foreach (int n in stopped) 
      { 
       _threads.Remove(n); 
      } 
     } 
    } 

    public void watch() 
    { 

     int numIntervals = 15 * 12; //15 minutes in 5 second intervals 

     while (_runWatchdog) 
     { 
      //cycle through the batches and check the matched threads. 
      foreach (KeyValuePair<int, MethodInfo> kvp in _batches) 
      { 
       //if they are not running 
       if (!_threads[kvp.Key].IsAlive) 
       { 
        //mark the thread failure and then try again. 
        ThreadFailure tf; 
        if (_failureCounts.ContainsKey(kvp.Key)) 
        { 
         tf = _failureCounts[kvp.Key]; 
        } 
        else 
        { 
         tf = new ThreadFailure(); 
        } 
        tf.Count++; 

        if (tf.Count >= 8) 
        { 
         //log an error as we've been trying to start this thread for 2 hours now 
         _logger.Error("Unable to start the thread: " + kvp.Value.Name + " ***** NOT TRYING AGAIN UNTIL SERVICE RESTART"); 
        } 
        else 
        { 
         _logger.Warn("Thread (" + kvp.Value.Name + ") found stopped... RESTARTING"); 
         startThread(kvp.Key, kvp.Value); 
        } 
       } 
      } 
      //sleep 15 minutes and repeat. 
      _logger.Info("*** Watcher sleeping for 15 minutes"); 
      for (int i = 1; i <= numIntervals; i++) 
      { 
       if (!_runWatchdog) 
        break; 
       Thread.Sleep(5000); //sleep for 5 seconds 
      } 
      _logger.Info("*** Watcher woke up."); 
     } 

     _logger.Info("Watcher thread stopping."); 
    } 

    public void setLogger(ILog l) 
    { 
     _logger = l; 
    } 

    #endregion 
} 

Итак, основная программа вызывает ContainerService.start(), которая вызывает IRunnableBatch.run(), который вызывает IWatchdog.startThreads(). Метод startThreads() обнаруживает и запускает все найденные им потоки, затем запускает поток, чтобы наблюдать за остальными в случае их смерти по какой-то причине. Затем функции полностью возвращаются к основной функции.

Теперь служба просто ждет, когда диспетчер службы вызовет OnStop(), но для целей тестирования у меня есть сбой основного потока в течение 1 минуты, а затем вызов ContainerService.stop().

После всего этого объяснения я теперь попал в проблему .... whew !!

Когда основной поток вызывает stop(), а метод stop() вызывает IRunnableBatch.stop(), если у меня есть точка останова и проверяю переменную _watchdog, я вижу, что все связанные с ней переменные-члены возвращаются к их начальные значения (без потоков, ни потока наблюдателя, ни партий, ничего ...).

У кого-нибудь есть идеи, почему?

+1

Thread micro-management: (( –

+1

1. Я не вижу блокировки в коде. Почему вы думаете, что это потокобезопасно? 2. Не используйте ArrayList, если вы не застряли в .NET 1.1 –

+0

Вы действительно уверены, что потоки были созданы? Добавьте еще трассировку, чтобы увидеть, что было действительно выполнено. –

ответ

0

Я вижу проблему. Прочитайте https://github.com/ninject/ninject/wiki/Multi-injection, и вы увидите, что GetAll возвращает перечислимое число, которое активирует ваши объекты при повторении, а не в списке. Поэтому в ContainerService.start создаются ваши запущенные пакетные объекты, и в конце создается целый новый набор объектов.

Попробуйте добавить .ToList() после вашего вызова GetAll или изменить конфигурацию Ninject, чтобы ваши runnables не были временными.

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