I have a place in my code, that I need to wait for a either finger to be identified on a sensor, or the user pressed a key to abort this action and return to the main menu.
I tried using something like conditional variables with Monitor and lock concepts but when I try to alert the main thread, nothing happens.
CODE:
private static object _syncFinger = new object(); // used for syncing
private static bool AttemptIdentify()
{
// waiting for either the user cancels or a finger is inserted
lock (_syncFinger)
{
Thread tEscape = new Thread(new ThreadStart(HandleIdentifyEscape));
Thread tIdentify = new Thread(new ThreadStart(HandleIdentify));
tEscape.IsBackground = false;
tIdentify.IsBackground = false;
tEscape.Start();
tIdentify.Start();
Monitor.Wait(_syncFinger); // -> Wait part
}
// Checking the change in the locked object
if (_syncFinger is FingerData) // checking for identity found
{
Console.WriteLine("Identity: {0}", ((FingerData)_syncFinger).Guid.ToString());
}
else if(!(_syncFinger is Char)) // char - pressed a key to return
{
return false; // returns with no error
}
return true;
}
private static void HandleIdentifyEscape()
{
do
{
Console.Write("Enter 'c' to cancel: ");
} while (Console.ReadKey().Key != ConsoleKey.C);
_syncFinger = new Char();
LockNotify((object)_syncFinger);
}
private static void HandleIdentify()
{
WinBioIdentity temp = null;
do
{
Console.WriteLine("Enter your finger.");
try // trying to indentify
{
temp = Fingerprint.Identify(); // returns FingerData type
}
catch (Exception ex)
{
Console.WriteLine("ERROR: " + ex.Message);
}
// if couldn't identify, temp would stay null
if(temp == null)
{
Console.Write("Invalid, ");
}
} while (temp == null);
_syncFinger = temp;
LockNotify(_syncFinger);
}
private static void LockNotify(object syncObject)
{
lock(syncObject)
{
Monitor.Pulse(syncObject);
}
}
when i try to alert the main thread, nothing happens.
That's because the main thread is waiting on the monitor for the object created here:
private static object _syncFinger = new object(); // used for syncing
But each of your threads replaces that object value, and then signals the monitor for the new object. The main thread has no knowledge of the new object, and so of course signaling the monitor for that new object will have no effect on the main thread.
First, any time you create an object for the purpose of using with lock, make it readonly:
private static readonly object _syncFinger = new object(); // used for syncing
It's always the right thing to do , and that will prevent you from ever making the mistake of changing the monitored object while a thread is waiting on it.
Next, create a separate field to hold the WinBioIdentity value, e.g.:
private static WinBioIdentity _syncIdentity;
And use that to relay the result back to the main thread:
private static bool AttemptIdentify()
{
// waiting for either the user cancels or a finger is inserted
lock (_syncFinger)
{
_syncIdentity = null;
Thread tEscape = new Thread(new ThreadStart(HandleIdentifyEscape));
Thread tIdentify = new Thread(new ThreadStart(HandleIdentify));
tEscape.IsBackground = false;
tIdentify.IsBackground = false;
tEscape.Start();
tIdentify.Start();
Monitor.Wait(_syncFinger); // -> Wait part
}
// Checking the change in the locked object
if (_syncIdentity != null) // checking for identity found
{
Console.WriteLine("Identity: {0}", ((FingerData)_syncIdentity).Guid.ToString());
return true;
}
return false; // returns with no error
}
private static void HandleIdentifyEscape()
{
do
{
Console.Write("Enter 'c' to cancel: ");
} while (Console.ReadKey().Key != ConsoleKey.C);
LockNotify((object)_syncFinger);
}
private static void HandleIdentify()
{
WinBioIdentity temp = null;
do
{
Console.WriteLine("Enter your finger.");
try // trying to indentify
{
temp = Fingerprint.Identify(); // returns FingerData type
}
catch (Exception ex)
{
Console.WriteLine("ERROR: " + ex.Message);
}
// if couldn't identify, temp would stay null
if(temp == null)
{
Console.Write("Invalid, ");
}
} while (temp == null);
__syncIdentity = temp;
LockNotify(_syncFinger);
}
All that said, you should prefer to use the modern async/await idiom for this:
private static bool AttemptIdentify()
{
Task<WinBioIdentity> fingerTask = Task.Run(HandleIdentify);
Task cancelTask = Task.Run(HandleIdentifyEscape);
if (Task.WaitAny(fingerTask, cancelTask) == 0)
{
Console.WriteLine("Identity: {0}", fingerTask.Result.Guid);
return true;
}
return false;
}
private static void HandleIdentifyEscape()
{
do
{
Console.Write("Enter 'c' to cancel: ");
} while (Console.ReadKey().Key != ConsoleKey.C);
}
private static WinBioIdentity HandleIdentify()
{
WinBioIdentity temp = null;
do
{
Console.WriteLine("Enter your finger.");
try // trying to indentify
{
temp = Fingerprint.Identify(); // returns FingerData type
}
catch (Exception ex)
{
Console.WriteLine("ERROR: " + ex.Message);
}
// if couldn't identify, temp would stay null
if(temp == null)
{
Console.Write("Invalid, ");
}
} while (temp == null);
return temp;
}
The above is a bare-minimum example. It would be better to make the AttemptIdentify() method async itself, and then use await Task.WhenAny() instead of Task.WaitAny(). It would also be better to include some mechanism to interrupt the tasks, i.e. once one has completed, you should want to interrupt the other so it's not lying around continuing to attempt its work.
But those kinds of issues are not unique to the async/await version, and don't need to be solved to improve on the code you have now.
Related
Suppose I have a long operation inside a subroutine or function and I want to be able to cancel (exit subroutine or function) immediately after a "cancellation flag" is set to true. What is the best way to do it? One way is to check the flag after each line of code but that is not very elegant.
For example:
dim _CancelFlag as boolean = false
Sub LongOperation()
dim a as integer
a = 1
if _CancelFlag = True Then
Exit Sub
End If
a = 2
if _CancelFlag = True Then
Exit Sub
End If
'And so on...
End Sub
Of course a = 1 is only for illustration purpose. Say the operation is really long until a = 100 and it is not possible to put them into a loop, how can I trigger the cancellation from outside of the subroutine and stop it immediately?
I was thinking to put the sub into a BackgroundWorker or Task but then I still have to check for CancellationToken somewhere inside the sub.. Do I really have to check after each line of code?
It depends on the granularity you want to achieve: how many seconds can you expect your method be canceled?
If the cancellation must take place "immediately" you have to check in as many place as you can. However, just checking before and after long sub steps of your operation is enough in the general case.
Remember that if you have to wait on handles, you have to use the appropriate overload that specifies a timeout or a cancellation token.
Additionally, you should propagate the cancellation token/your flag deep down your methods to allow detection early the cancellation requests.
I found a more elegant way to do it, although it does use a loop in the end. Please let me know if anybody has an better solution. I will also update when I find something else.
Sub LongOperation()
dim state as integer = 0
Do while state < 100
Select Case state
Case 0
a = 1
Case 1
a = 2
Case Else
Exit do
End Select
If _CancelFlag = True Then
Exit Sub
End If
state += 1
Loop
End Sub
This is a sample windows application I have created to cancel or pause the log running task.
public partial class Form1 : Form
{
updateUI _updateGUI;
CancellationToken _cancelToken;
PauseTokenSource _pauseTokeSource;
public Form1()
{
InitializeComponent();
}
delegate void updateUI(dynamic value);
private void btnStartAsync_Click(object sender, EventArgs e)
{
_pauseTokeSource = new PauseTokenSource();
_cancelToken = default(CancellationToken);
_pauseTokeSource.onPause -= _pauseTokeSource_onPause;
_pauseTokeSource.onPause += _pauseTokeSource_onPause;
Task t = new Task(() => { LongRunning(_pauseTokeSource); }, _cancelToken);
t.Start();
}
private void _pauseTokeSource_onPause(object sender, PauseEventArgs e)
{
var message = string.Format("Task {0} at {1}", e.Paused ? "Paused" : "Resumed", DateTime.Now.ToString());
this.Invoke(_updateGUI, message);
}
private async void LongRunning(PauseTokenSource pause)
{
_updateGUI = new updateUI(SetUI);
for (int i = 0; i < 20; i++)
{
await pause.WaitWhilePausedAsync();
Thread.Sleep(500);
this.Invoke(_updateGUI, i.ToString() + " => " + txtInput.Text);
//txtOutput.AppendText(Environment.NewLine + i.ToString());
if (_cancelToken.IsCancellationRequested)
{
this.Invoke(_updateGUI, "Task cancellation requested at " + DateTime.Now.ToString());
break;
}
}
_updateGUI = null;
}
private void SetUI(dynamic output)
{
//txtOutput.AppendText(Environment.NewLine + count.ToString() + " => " + txtInput.Text);
txtOutput.AppendText(Environment.NewLine + output.ToString());
}
private void btnCancelTask_Click(object sender, EventArgs e)
{
_cancelToken = new CancellationToken(true);
}
private void btnPause_Click(object sender, EventArgs e)
{
_pauseTokeSource.IsPaused = !_pauseTokeSource.IsPaused;
btnPause.Text = _pauseTokeSource.IsPaused ? "Resume" : "Pause";
}
}
public class PauseTokenSource
{
public delegate void TaskPauseEventHandler(object sender, PauseEventArgs e);
public event TaskPauseEventHandler onPause;
private TaskCompletionSource<bool> _paused;
internal static readonly Task s_completedTask = Task.FromResult(true);
public bool IsPaused
{
get { return _paused != null; }
set
{
if (value)
{
Interlocked.CompareExchange(ref _paused, new TaskCompletionSource<bool>(), null);
}
else
{
while (true)
{
var tcs = _paused;
if (tcs == null) return;
if (Interlocked.CompareExchange(ref _paused, null, tcs) == tcs)
{
tcs.SetResult(true);
onPause?.Invoke(this, new PauseEventArgs(false));
break;
}
}
}
}
}
public PauseToken Token
{
get
{
return new PauseToken(this);
}
}
internal Task WaitWhilePausedAsync()
{
var cur = _paused;
if (cur != null)
{
onPause?.Invoke(this, new PauseEventArgs(true));
return cur.Task;
}
return s_completedTask;
}
}
public struct PauseToken
{
private readonly PauseTokenSource m_source;
internal PauseToken(PauseTokenSource source) { m_source = source; }
public bool IsPaused { get { return m_source != null && m_source.IsPaused; } }
public Task WaitWhilePausedAsync()
{
return IsPaused ?
m_source.WaitWhilePausedAsync() :
PauseTokenSource.s_completedTask;
}
}
public class PauseEventArgs : EventArgs
{
public PauseEventArgs(bool paused)
{
Paused = paused;
}
public bool Paused { get; private set; }
}
If your LongOperation() is well splittable into short operations (I assume a=1, a=2, ..., a=100 being all reasonably short) than you could wrap all the short operations into Tasks, put them into a TaskQueue and process that queue, checking between the Tasks if cancellation was requested.
If LongOperation() is difficult to split you could run the LongOperation() on a separate dedicated thread and abort that thrad on cancellation. Some have commented aborting a thread being dirty and not being recommended. Actually that's not that bad, if properly handled. Aborting a thread just raises a ThradAbortException within the thread method. So if there is a try - catch - finally in the LongOperation(), catching and handling the exception and if the finally code properly does cleanup, closes all handles, disposes etc., this should be ok and nothing to be afraid of.
How do I close down and wait for a semaphore to be fully released?
private SemaphoreSlim _processSemaphore = new SemaphoreSlim(10);
public async Task<Modification> Process(IList<Command> commands)
{
Assert.IsFalse(_shuttingDown, "Server is in shutdown phase");
await _processSemaphore.WaitAsync();
try
{
// threads that have reached this far must be allowed to complete
return _database.Process(commands);
}
finally
{
_processSemaphore.Release();
}
}
public async Task StopAsync()
{
_shuttingDown = true;
// how wait for threads to complete without cancelling?
await ?
}
private SemaphoreSlim _processSemaphore = new SemaphoreSlim(10);
private int _concurrency;
private TaskCompletionSource<int> _source;
private ManualResetEvent _awaitor;
public void Start()
{
//solution 1
_concurrency = 0;
_source = new TaskCompletionSource<int>();
_shuttingDown = false;
//solution 2
_awaitor = new ManualResetEvent(false);
//your code
}
public async Task<Modification> Process(IList<Command> commands)
{
Interlocked.Increment(ref _concurrency);
Assert.IsFalse(_shuttingDown, "Server is in shutdown phase");
await _processSemaphore.WaitAsync();
try
{
// threads that have reached this far must be allowed to complete
return _database.Process(commands);
}
finally
{
_processSemaphore.Release();
//check and release
int concurrency = Interlocked.Decrement(ref _concurrency);
if (_shuttingDown && concurrency == 0)
{
//solution 1
_source.TrySetResult(0);
//solution 2
_awaitor.Set();
}
}
}
public async Task StopAsync()
{
_shuttingDown = true;
// how wait for threads to complete without cancelling?
if (Interlocked.CompareExchange(ref _concurrency, 0, 0) != 0)
{
await _source.Task;//solution 1
_awaitor.WaitOne();//solution 2
}
}
Might not be exactly what you need, but I had a similar case and I solved it with the CountdownEvent class
private CountdownEvent _countdownEvent = new CountdownEvent(1);
process_method() {
//if the count is zero means that we already called finalize
if (_countdownEvent.IsSet)
return;
try
{
//this can throw and exception if we try to add when the countdown has already reached 0.
//this exception happens when one process_method B has passed the _counddownEvent.IsSet check and context switched to
//to another process_method A that was the last one (after finalize waits for 0) and sets the countdown to 0. Which
//triggers finalization and should not allow new process_method, so process_methodB not continuing is good (finalization is
//in progress).
_countdownEvent.AddCount(1);
} catch
{
return;
}
try
{
//your process
}
finally
{
_countdownEvent.Signal();
}
}
And then when you are ready to wait for the count to be zero:
finalize() {
_countdownEvent.Signal();
_countdownEvent.Wait(_finalizationSafetyTimeout, cancellationToken)
}
I am trying to use a batch statement instead of a single binded insert statement.
Even though this is a very small change this fails and I am not looking for a good way for the error handling and to find out which part is the issue. One issue is definetly that the Java API has a getStatements method which is missing in the C# driver.
The pseudo code looks like this:
private BatchStatement batchStatement = new BatchStatement();
private const int blockingFactor = 5;
private int i = 0;
private object locker = new object();
public CassandraBufferHandler()
{
Cluster = Cluster.Builder().AddContactPoints("localhost").Build();
Session = Cluster.Connect("my_keyspace");
InsertStatement = Session.Prepare("Insert into ticks (instrumentcode, timestamp, type, exchange, price, volume) values(?,?,?,?,?,?) if not exists;");
}
public void OnEvent(TickCassandra tickCassandra, long sequence, bool endOfBatch)
{
try
{
lock (locker)
batchStatement.Add(
InsertStatement.Bind(tickCassandra.Instrumentcode,
tickCassandra.Timestamp,
tickCassandra.Type,
tickCassandra.Exchange,
tickCassandra.Price,
tickCassandra.Volume));
if (i++ % blockingFactor == 0)
{
BatchStatement tmp;
lock (locker)
{
tmp = batchStatement;
tmp.EnableTracing();
batchStatement = new BatchStatement();
}
Session.ExecuteAsync(tmp).ContinueWith(t =>
{
if (t.Exception != null)
{
ErrorCount++;
Log.Error(t.Exception.Message + tmp.ToString());
}
else
InsertCount++;
});
}
}
catch (Exception ex)
{
Log.Error("Exception:" + ex);
Active = false;
}
I wrote a short Program which searches for empty directories and deletes them.
This process should run in background while a second process should write something to the Console every second so that the user knows the program is still running.
My problem is that the whole program stops after about 3 seconds while the processDirectory method didn't even finish.
My Main Method which calls a Method (processDirectory()) which runs in a second Thread:
static void Main(string[] args)
{
Thread delEmpty = new Thread(() => Thread2.processDirectory(#"C:\Users\Mani\Documents"));
delEmpty.Start();
printRunning(delEmpty);
File.WriteAllLines(#"C:\Users\Mani\Desktop\Unauthorized Folders.txt", Thread2.unauthorized);
File.WriteAllLines(#"C:\Users\Mani\Desktop\Empty Folders.txt", Thread2.emptyFolders);
Console.ReadKey();
}
My second Class which stores my processDirectory Method which should run in background:
public static List<string> unauthorized = new List<string>();
public static List<string> emptyFolders = new List<string>();
public static void processDirectory(string rootPath)
{
if (!Directory.Exists(rootPath)) return;
foreach (var dir in Directory.GetDirectories(rootPath))
{
try
{
processDirectory(dir);
if (Directory.GetFiles(dir).Length == 0 && Directory.GetDirectories(dir).Length == 0) Directory.Delete(dir, false);
}
catch (UnauthorizedAccessException uae) { unauthorized.Add(uae.Message); }
}
}
Code for printing something:
static async void printRunning(Thread delEmpty)
{
Console.CursorVisible = false;
for (int cnt = 1; delEmpty.IsAlive; cnt++)
{
switch (cnt)
{
case 1:
Console.Write("Running. ");
break;
case 2:
Console.Write("Running . ");
break;
case 3:
Console.Write("Running .");
cnt = 0;
break;
}
await Task.Delay(1000);
}
Console.Write("Finished!");
Console.CursorVisible = true;
}
I'm going to suggest that you avoid using threads and use an abstraction that deals with your threading issues for you. I suggest making use of Microsoft's Reactive Framework Team's Reactive Extensions (NuGet "System.Reactive") and Interactive Extensions (NuGet "System.Interactive").
Then you can do this:
static void Main(string[] args)
{
var rootPath = #"C:\Users\Mani\Documents";
using (Observable
.Interval(TimeSpan.FromSeconds(1.0))
.Subscribe(x => Console.WriteLine($"Running{"".PadLeft((int)x % 3)}.")))
{
Thread2.processDirectory(rootPath);
}
}
public static class Thread2
{
public static List<string> unauthorized = new List<string>();
public static List<string> emptyFolders = null;
public static void processDirectory(string rootPath)
{
if (!Directory.Exists(rootPath)) return;
emptyFolders =
EnumerableEx
.Expand(Directory.GetDirectories(rootPath), dir => Directory.GetDirectories(dir))
.Where(dir => Directory.GetFiles(dir).Length == 0 && Directory.GetDirectories(dir).Length == 0)
.ToList();
emptyFolders
.AsEnumerable()
.Reverse()
.ForEach(dir =>
{
try
{
Directory.Delete(dir, false);
}
catch (UnauthorizedAccessException uae) { unauthorized.Add(uae.Message); }
});
}
}
The key elements here are:
the Observable.Interval that sets up a timer to display the "Running" message every second.
the EnumerableEx.Expand which recursively builds the list of folders to be deleted.
the Reverse/ForEach which runs through the folders to be deleted (in reverse order) and deletes them.
It's important to not that the deleting happens on the main thread - it's just the "Running" message that comes out on the other thread. If needed, though, it would be fairly easy to push the deleting to another thread, but it isn't necessary.
To handle the case when GetDirectories throws an error, use this code:
Func<string, string[]> getDirectories = dir =>
{
try
{
return Directory.GetDirectories(dir);
}
catch (UnauthorizedAccessException uae)
{
unauthorized.Add(uae.Message);
return new string[] { };
}
};
emptyFolders =
EnumerableEx
.Expand(getDirectories(rootPath), dir => getDirectories(dir))
.Where(dir => Directory.GetFiles(dir).Length == 0 && getDirectories(dir).Length == 0)
.ToList();
You can solve the issue by on of two ways:
Make your method printRunning run synchronously
Add delEmpty.Join() so main thread will wait while delEmpty thread finishes
delEmpty.Start();
printRunning(delEmpty);
delEmpty.Join();
In case of 1st solution replace the printRunning method with the following one
static void printRunning(Thread delEmpty)
{
Console.CursorVisible = false;
for (int cnt = 0; delEmpty.IsAlive; cnt++)
{
switch (cnt % 3)
{
case 0:
Console.Write("Running.");
break;
case 1:
Console.Write("Running..");
break;
case 2:
Console.Write("Running...");
break;
}
Thread.Sleep(1000);
Console.SetCursorPosition(0, 0);
Console.Clear();
}
Console.Write("Finished!");
Console.CursorVisible = true;
}
I'm currently seeing some off behaviors as I'm working on a multithreaded windows service. The issue that I'm having is that some objects appear to be resetting when accessed from different threads.
Let me demonstrate with some code (simplified to explain the problem)....
First, I have a class that launches threads based on methods in another class (using Ninject to get the classes) and then later stops them:
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();
}
}
}
Now, within the run() method of an IRunnableBatch class I have something like this:
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();
}
}
And here's the code for the Watchdog class:
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
}
So, the main program calls ContainerService.start() which calls the IRunnableBatch.run(), which calls IWatchdog.startThreads(). The startThreads() method locates and launches all of the threads it finds, then launches a thread to watch the others in case they die for some reason. Then the functions exit all the way back up the the main function.
Now, a service simply waits for the service manager to call OnStop() but for testing purposes I have the main thread sleep for 1 minute then call ContainerService.stop().
After all of that explanation, I now get to the issue.... whew!!
When the main thread calls stop(), and the stop() method calls IRunnableBatch.stop(), if I have a breakpoint there and examine the _watchdog variable I see that all of it's associated member variables are set back to their initial values (no threads, no watcher thread, no batches, nothing...).
Anyone have any ideas why?
I see the problem. Read https://github.com/ninject/ninject/wiki/Multi-injection, and you'll see that GetAll returns an enumerable that activates your objects as you iterate, not a list. Therefore, in ContainerService.start, your runnable batch objects are created, and in stop, a whole new set of objects are created.
Try adding a .ToList() after your call to GetAll, or change your Ninject config so that your runnables are not transient.