В настоящее время я вижу некоторые действия, когда я работаю над многопоточным сервисом 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, я вижу, что все связанные с ней переменные-члены возвращаются к их начальные значения (без потоков, ни потока наблюдателя, ни партий, ничего ...).
У кого-нибудь есть идеи, почему?
Thread micro-management: (( –
1. Я не вижу блокировки в коде. Почему вы думаете, что это потокобезопасно? 2. Не используйте ArrayList, если вы не застряли в .NET 1.1 –
Вы действительно уверены, что потоки были созданы? Добавьте еще трассировку, чтобы увидеть, что было действительно выполнено. –