I am implementing a logger which writes records to the database. In order to prevent the database writes from blocking the code which is calling the logger, I've moved the DB access to a separate thread, implemented using a producer/consumer model based on BlockingCollection<string>.
Here's the simplified implementation:
abstract class DbLogger : TraceListener
{
private readonly BlockingCollection<string> _buffer;
private readonly Task _writerTask;
DbLogger()
{
this._buffer = new BlockingCollection<string>(new ConcurrentQueue<string>(), 1000);
this._writerTask = Task.Factory.StartNew(this.ProcessBuffer, TaskCreationOptions.LongRunning);
}
// Enqueue the msg.
public void LogMessage(string msg) { this._buffer.Add(msg); }
private void ProcessBuffer()
{
foreach (string msg in this._buffer.GetConsumingEnumerable())
{
this.WriteToDb(msg);
}
}
protected abstract void WriteToDb(string msg);
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Signal to the blocking collection that the enumerator is done.
this._buffer.CompleteAdding();
// Wait for any in-progress writes to finish.
this._writerTask.Wait(timeout);
this._buffer.Dispose();
}
base.Dispose(disposing);
}
}
Now, when my application shuts down, I need to make sure that the buffer is flushed before the database connection goes down. Otherwise, WriteToDb will throw an exception.
So, here's my naive Flush implementation:
public void Flush()
{
// Sleep until the buffer is empty.
while(this._buffer.Count > 0)
{
Thread.Sleep(50);
}
}
The problem with this implementation is the following sequence of events:
There is one entry in the buffer.
In the logging thread, MoveNext() is called on the enumerator, so we're now in the body of ProcessBuffer's foreach loop.
Flush() is called by the main thread. It sees that the collection is empty, so returns immediately.
The main thread closes the DB connection.
Back in the logging thread, the body of the foreach loop starts executing. WriteToDb is called, and fails because the DB connection has been closed.
So, my next try was adding some flags, like so:
private volatile bool _isWritingBuffer = false;
private void ProcessBuffer()
{
foreach (string msg in this._buffer.GetConsumingEnumerable())
{
lock (something) this._isWritingBuffer = true;
this.WriteToDb(msg);
lock (something) this._isWritingBuffer = false;
}
}
public void Flush()
{
// Sleep until the buffer is empty.
bool isWritingBuffer;
lock(something) isWritingBuffer = this._isWritingBuffer;
while(this._buffer.Count > 0 || isWritingBuffer)
{
Thread.Sleep(50);
}
}
However, there's still a race condition, since the entire Flush() method could execute after the collection is empty but before _isWritingBuffer gets set to true.
How can I fix my Flush implementation to avoid this race condition?
Note: For various reasons, I must write the logger from scratch, so please don't answer with a suggestion that I use some existing logging framework.
First never ever lock on a public object, especially this.
Moreover never ever use bare booleans for synchronization: see my blog if you want to have a glimpse in what can go wrong: Synchronization, memory visibility and leaky abstractions :)
Concerning the issue itself I must be missing something but why do you need such a Flush method?
Indeed when you're done with your logging you will dispose of the logger by calling its Dispose method from the main thread.
And you have implemented it in such a way that it will wait for the "write to DB" task.
If I'm wrong and you really need to synchronize with another primitive then you should use an event:
In the DbLogger:
public ManualResetEvent finalizing { get; set; }
public void Flush()
{
finalizing.WaitOne();
}
And somewhere, e.g. in ProcessBuffer you notify when you're done with writing to DB:
finalizing.Set();
Related
I have a LongOperationHelper that I activate on each potentially long operation.
It displays a semi transparent layer and does not allow any click until the operation ends and a spinning control to indicate progress.
It looks something like that (missing some business logic but the idea is clear I think):
Edited: (Added the missing code of the common states that actually needed the locking - this is more like the problematic code)
(My solution is posted in an answer bellow)
public static class LongOperationHelper
{
private static object _synchObject = new object();
private static Dictionary<string, int> _calls = new Dictionary<string, int>();
private static Action<string> DisplayLongOperationRequested;
private static Action<string> StopLongOperationRequested;
public static void Begin(string messageKey)
{
lock (_synchObject)
{
if (_calls.ContainsKey(messageKey))
{
_calls[messageKey]++;
}
else
{
_calls.Add(messageKey, 1);
DispatcherHelper.InvokeIfNecesary(() =>
{
//Raise event for the MainViewModel to display the long operation layer
DisplayLongOperationRequested?.Invoke(messageKey);
});
}
}
}
public static void End(string messageKey)
{
lock (_synchObject)
{
if (_calls.ContainsKey(messageKey))
{
if (_calls[messageKey] > 1)
{
_calls[messageKey]--;
}
else
{
_calls.Remove(messageKey);
DispatcherHelper.InvokeIfNecesary(() =>
{
//Raise event for the MainViewModel to stop displaying the long operation layer
StopLongOperationRequested?.Invoke(messageKey);
});
}
}
else
{
throw new Exception("Cannot End long operation that has not began");
}
}
}
}
So as you can probably see, there is a potential deadlock in there, if:
Someone calls Begin from a non UI thread.
It enters the lock
Someone calls Begin or End from a UI thread and gets locked
The first Begin call tries to Dispatch to the UI thread.
Result: Deadlock!
I want to make this Helper thread safe, so that any thread might call Begin, or End at any given time, interested to see if there is any known pattern, any Ideas?
Thanks!
Don't lock for the entire method. Lock only when you touch the fields that need it, and unlock as soon as you're done. Lock and unlock each time you touch those fields. Otherwise, you'll end up with deadlocks like this.
You can also consider using ReaderWriterLockSlim, which differentiates between read locks and write locks. It lets multiple threads read at the same time, but locks everyone out when a write lock is taken. There is an example on how to use it in that documentation.
The entire purpose of having a "UI thread" is to avoid synchronization exactly like this. The fact that all UI code needs to run on a single thread means that it, by definition, cannot run concurrently. You have no need to use locks to make your UI code run atomically because it's all running on a single thread.
Writing UI code that required the programmer to do their own locking is sufficiently hard and error prone that the whole framework was designed around the idea that it's unreasonable to expect people to do it (correctly), and that it's much easier to simply force all UI code to go into a single thread, where other synchronization mechanisms won't be needed.
Here is the "Deadlock free" code:
I have relocated the dispatching to the UI thread to outside of the lock.
(Can someone still see a potential deadlock here?)
public static class LongOperationHelper
{
private static object _synchObject = new object();
private static Dictionary<string, int> _calls = new Dictionary<string, int>();
private static Action<string> DisplayLongOperationRequested;
private static Action<string> StopLongOperationRequested;
public static void Begin(string messageKey)
{
bool isRaiseEvent = false;
lock (_synchObject)
{
if (_calls.ContainsKey(messageKey))
{
_calls[messageKey]++;
}
else
{
_calls.Add(messageKey, 1);
isRaiseEvent = true;
}
}
//This code got out of the lock, therefore cannot create a deadlock
if (isRaiseEvent)
{
DispatcherHelper.InvokeIfNecesary(() =>
{
//Raise event for the MainViewModel to display the long operation layer
DisplayLongOperationRequested?.Invoke(messageKey);
});
}
}
public static void End(string messageKey)
{
bool isRaiseEvent = false;
lock (_synchObject)
{
if (_calls.ContainsKey(messageKey))
{
if (_calls[messageKey] > 1)
{
_calls[messageKey]--;
}
else
{
_calls.Remove(messageKey);
isRaiseEvent = true;
}
}
else
{
throw new Exception("Cannot End long operation that has not began");
}
}
//This code got out of the lock, therefore cannot create a deadlock
if (isRaiseEvent)
{
DispatcherHelper.InvokeIfNecesary(() =>
{
StopLongOperationRequested?.Invoke(messageKey);
});
}
}
}
I have a client - server application and I want to check periodically if client has disconnected from server.
I have decided that I will check for incoming packets. If i received any in time span of let say 15 seconds I have a valid connection,
if not I have disconnected and will try to reconnect.
So far I have this sample code (this is sample recreated from my code):
namespace TimerExample
{
class Program
{
static void Main(string[] args)
{
HandlePackets();
}
public void HandlePackets()
{
//code that handles incomming packets
foo test = new foo();
test.StartThread();
}
}
class foo
{
public bool _isRunning { get; set; }
private Stopwatch sw { get; set; }
public void StartThread()
{
this._isRunning = true;
new Thread(new ThreadStart(this.DoWork)).Start();
this.sw.Restart();
}
public void StopThread()
{
this._isRunning = false;
this.sw.Stop();
}
private void DoWork()
{
while (this._isRunning)
{
Console.WriteLine("Elapsed in miliseconds: " + this.GetRuntime().ToString());
if (GetRuntime() > 15000)
{
Console.WriteLine("Client disconnected.... restarting");
this.StopThread();
}
Thread.Sleep(1000);
}
}
public long GetRuntime()
{
return this.sw.ElapsedMilliseconds;
}
public foo()
{
_isRunning = false;
sw = new Stopwatch();
}
}
}
What I want for code to be doing is: Function HandlePackets will be executed every time packet will arrive. Inside that function I will call
function StartThread which will run Stopwatch in separate thread and this process will go on as long as stopwatch elapsed time in milliseconds
won't be bigger than lets say 15 seconds.
If it will I will call Reconnect.
So basically timer will restart every time a packet is received and reconnect will be called if ElapsedTime will be greater than 15 seconds.
There are several ways to implement this mechanism.
Creating thread is the worst one.
Be careful - accessing Stopwatch instance members from multiple threads is not safe.
One easy and straightforward solution is to create ThreadPool Timer that ticks let's say every 15 seconds and checks boolean variable via Volatile.Read. Once boolean variable is False - you can re-connect.
From receiver thread you just need to set variable using Volatile.Write true. This does not consume resources when receiving (almost).
In many of the implementations could be races because of re-connection mechanism that can start a moment before new packet arrives. The easiest and rogue way to improve this is to stop timer right before you decide to re-connect and start it again once connection is done. You must understand that there is no way to solve this false-reconnection issue.
The method above works pretty much like WatchDog
From design perspective I would recommend you create classes : Receiver and WatchDog and ConnectionManager
// Receives and processes data
class Receiver : IDisposable
{
public Receiver(WatchDog watchDog);
public void LoopReceive(); // Tick watch dog on every packet
public void Dispose();
}
// Setups timer and periodically checks if receiver is alive.
// If its not, it asks manager to reconnect and disposes receiver
class WatchDog : IDisposable
{
public WatchDog(ConnectionFactory factory);
// Setups timer, performs Volatile.Read and if receiver is dead, call dispose on it and ask manager to reconnect.
public void StartWatching(IDisposable subject);
public void Tick(); // Volatile.Write
public void Dispose();
}
// Can re-connect and create new instances of timer and watchdog
// Holds instance variable of receiver created
class ConnectionManager
{
public void Connect();
// disposes watch dog and calls connect
public void ReConnect(WatchDog watchDog);
}
PS: Volatile.* could be replaced with volatile keyword for the flag variable
I'm trying to log / report all unhandled exceptions in my app (error reporting solution). I've come across a scenario that is always unhandled. I'm wondering how would I catch this error in an unhandled manner. Please note that I've done a ton of research this morning and tried a lot of things.. Yes, I've seen this, this and many more. I'm just looking for a generic solution to log unhandled exceptions.
I have the following code inside of a console test apps main method:
Task.Factory.StartNew(TryExecute);
or
Task.Run((Action)TryExecute);
as well as the following method:
private static void TryExecute() {
throw new Exception("I'm never caught");
}
I'm already tried wiring up to the following in my app, but they are never called.
AppDomain.CurrentDomain.UnhandledException
TaskScheduler.UnobservedTaskException
In my Wpf app where I initially found this error I also wired up to these events but it was never called.
Dispatcher.UnhandledException
Application.Current.DispatcherUnhandledException
System.Windows.Forms.Application.ThreadException
The only handler that is called ever is:
AppDomain.CurrentDomain.FirstChanceException
but this is not a valid solution as I only want to report uncaught exceptions (not every exception as FirstChanceException is called before any catch blocks are ever executed / resolved.
The TaskScheduler.UnobservedTaskException event should give you what you want, as you stated above. What makes you think that it is not getting fired?
Exceptions are caught by the task and then re-thrown, but not immediately, in specific situations. Exceptions from tasks are re-thrown in several ways (off the top of my head, there are probably more).
When you try and access the result (Task.Result)
Calling Wait(), Task.WaitOne(), Task.WaitAll() or another related Wait method on the task.
When you try to dispose the Task without explicitly looking at or handling the exception
If you do any of the above, the exception will be rethrown on whatever thread that code is running on, and the event will not be called since you will be observing the exception. If you don't have the code inside of a try {} catch {}, you will fire the AppDomain.CurrentDomain.UnhandledException, which sounds like what might be happening.
The other way the exception is re-thrown would be:
When you do none of the above so that the task still views the exception as unobserved and the Task is getting finalized. It is thrown as a last ditch effort to let you know there was an exception that you didn't see.
If this is the case and since the finalizer is non-deterministic, are you waiting for a GC to happen so that those tasks with unobserved exceptions are put in the finalizer queue, and then waiting again for them to be finalized?
EDIT: This article talks a little bit about this. And this article talks about why the event exists, which might give you insight into how it can be used properly.
I used the LimitedTaskScheduler from MSDN to catch all exceptions, included from other threads using the TPL:
public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
{
/// Whether the current thread is processing work items.
[ThreadStatic]
private static bool currentThreadIsProcessingItems;
/// The list of tasks to be executed.
private readonly LinkedList tasks = new LinkedList(); // protected by lock(tasks)
private readonly ILogger logger;
/// The maximum concurrency level allowed by this scheduler.
private readonly int maxDegreeOfParallelism;
/// Whether the scheduler is currently processing work items.
private int delegatesQueuedOrRunning; // protected by lock(tasks)
public LimitedConcurrencyLevelTaskScheduler(ILogger logger) : this(logger, Environment.ProcessorCount)
{
}
public LimitedConcurrencyLevelTaskScheduler(ILogger logger, int maxDegreeOfParallelism)
{
this.logger = logger;
if (maxDegreeOfParallelism Gets the maximum concurrency level supported by this scheduler.
public override sealed int MaximumConcurrencyLevel
{
get { return maxDegreeOfParallelism; }
}
/// Queues a task to the scheduler.
/// The task to be queued.
protected sealed override void QueueTask(Task task)
{
// Add the task to the list of tasks to be processed. If there aren't enough
// delegates currently queued or running to process tasks, schedule another.
lock (tasks)
{
tasks.AddLast(task);
if (delegatesQueuedOrRunning >= maxDegreeOfParallelism)
{
return;
}
++delegatesQueuedOrRunning;
NotifyThreadPoolOfPendingWork();
}
}
/// Attempts to execute the specified task on the current thread.
/// The task to be executed.
///
/// Whether the task could be executed on the current thread.
protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// If this thread isn't already processing a task, we don't support inlining
if (!currentThreadIsProcessingItems)
{
return false;
}
// If the task was previously queued, remove it from the queue
if (taskWasPreviouslyQueued)
{
TryDequeue(task);
}
// Try to run the task.
return TryExecuteTask(task);
}
/// Attempts to remove a previously scheduled task from the scheduler.
/// The task to be removed.
/// Whether the task could be found and removed.
protected sealed override bool TryDequeue(Task task)
{
lock (tasks)
{
return tasks.Remove(task);
}
}
/// Gets an enumerable of the tasks currently scheduled on this scheduler.
/// An enumerable of the tasks currently scheduled.
protected sealed override IEnumerable GetScheduledTasks()
{
var lockTaken = false;
try
{
Monitor.TryEnter(tasks, ref lockTaken);
if (lockTaken)
{
return tasks.ToArray();
}
else
{
throw new NotSupportedException();
}
}
finally
{
if (lockTaken)
{
Monitor.Exit(tasks);
}
}
}
protected virtual void OnTaskFault(AggregateException exception)
{
logger.Error(exception);
}
///
/// Informs the ThreadPool that there's work to be executed for this scheduler.
///
private void NotifyThreadPoolOfPendingWork()
{
ThreadPool.UnsafeQueueUserWorkItem(ExcuteTask, null);
}
private void ExcuteTask(object state)
{
// Note that the current thread is now processing work items.
// This is necessary to enable inlining of tasks into this thread.
currentThreadIsProcessingItems = true;
try
{
// Process all available items in the queue.
while (true)
{
Task item;
lock (tasks)
{
// When there are no more items to be processed,
// note that we're done processing, and get out.
if (tasks.Count == 0)
{
--delegatesQueuedOrRunning;
break;
}
// Get the next item from the queue
item = tasks.First.Value;
tasks.RemoveFirst();
}
// Execute the task we pulled out of the queue
TryExecuteTask(item);
if (!item.IsFaulted)
{
continue;
}
OnTaskFault(item.Exception);
}
}
finally
{
// We're done processing items on the current thread
currentThreadIsProcessingItems = false;
}
}
}
And than the "registration" of the TaskScheduler as the default using Reflection:
public static class TaskLogging
{
private const BindingFlags StaticBinding = BindingFlags.Static | BindingFlags.NonPublic;
public static void SetScheduler(TaskScheduler taskScheduler)
{
var field = typeof(TaskScheduler).GetField("s_defaultTaskScheduler", StaticBinding);
field.SetValue(null, taskScheduler);
SetOnTaskFactory(new TaskFactory(taskScheduler));
}
private static void SetOnTaskFactory(TaskFactory taskFactory)
{
var field = typeof(Task).GetField("s_factory", StaticBinding);
field.SetValue(null, taskFactory);
}
}
When using async/await in C#, the general rule is to avoid async void as that's pretty much a fire and forget, rather a Task should be used if no return value is sent from the method. Makes sense. What's strange though is that earlier in the week I was writing some unit tests for a few async methods I wrote, and noticed that NUnit suggested to mark the async tests as either void or returning Task. I then tried it, and sure enough, it worked. This seemed really strange, as how would the nunit framework be able to run the method and wait for all asynchronous operations to complete? If it returns Task, it can just await the task, and then do what it needs to do, but how can it pull it off if it returns void?
So I cracked open the source code and found it. I can reproduce it in a small sample, but I simply cannot make sense of what they're doing. I guess I don't know enough about the SynchronizationContext and how that works. Here's the code:
class Program
{
static void Main(string[] args)
{
RunVoidAsyncAndWait();
Console.WriteLine("Press any key to continue. . .");
Console.ReadKey(true);
}
private static void RunVoidAsyncAndWait()
{
var previousContext = SynchronizationContext.Current;
var currentContext = new AsyncSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(currentContext);
try
{
var myClass = new MyClass();
var method = myClass.GetType().GetMethod("AsyncMethod");
var result = method.Invoke(myClass, null);
currentContext.WaitForPendingOperationsToComplete();
}
finally
{
SynchronizationContext.SetSynchronizationContext(previousContext);
}
}
}
public class MyClass
{
public async void AsyncMethod()
{
var t = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
Console.WriteLine("Done sleeping!");
});
await t;
Console.WriteLine("Done awaiting");
}
}
public class AsyncSynchronizationContext : SynchronizationContext
{
private int _operationCount;
private readonly AsyncOperationQueue _operations = new AsyncOperationQueue();
public override void Post(SendOrPostCallback d, object state)
{
_operations.Enqueue(new AsyncOperation(d, state));
}
public override void OperationStarted()
{
Interlocked.Increment(ref _operationCount);
base.OperationStarted();
}
public override void OperationCompleted()
{
if (Interlocked.Decrement(ref _operationCount) == 0)
_operations.MarkAsComplete();
base.OperationCompleted();
}
public void WaitForPendingOperationsToComplete()
{
_operations.InvokeAll();
}
private class AsyncOperationQueue
{
private bool _run = true;
private readonly Queue _operations = Queue.Synchronized(new Queue());
private readonly AutoResetEvent _operationsAvailable = new AutoResetEvent(false);
public void Enqueue(AsyncOperation asyncOperation)
{
_operations.Enqueue(asyncOperation);
_operationsAvailable.Set();
}
public void MarkAsComplete()
{
_run = false;
_operationsAvailable.Set();
}
public void InvokeAll()
{
while (_run)
{
InvokePendingOperations();
_operationsAvailable.WaitOne();
}
InvokePendingOperations();
}
private void InvokePendingOperations()
{
while (_operations.Count > 0)
{
AsyncOperation operation = (AsyncOperation)_operations.Dequeue();
operation.Invoke();
}
}
}
private class AsyncOperation
{
private readonly SendOrPostCallback _action;
private readonly object _state;
public AsyncOperation(SendOrPostCallback action, object state)
{
_action = action;
_state = state;
}
public void Invoke()
{
_action(_state);
}
}
}
When running the above code, you'll notice that the Done Sleeping and Done awaiting messages show up before the Press any key to continue message, which means the async method is somehow being waited on.
My question is, can someone care to explain what's happening here? What exactly is the SynchronizationContext (I know it's used to post work from one thread to another) but I'm still confused as to how we can wait for all the work to be done. Thanks in advance!!
A SynchronizationContext allows posting work to a queue that is processed by another thread (or by a thread pool) -- usually the message loop of the UI framework is used for this.
The async/await feature internally uses the current synchronization context to return to the correct thread after the task you were waiting for has completed.
The AsyncSynchronizationContext class implements its own message loop. Work that is posted to this context gets added to a queue.
When your program calls WaitForPendingOperationsToComplete();, that method runs a message loop by grabbing work from the queue and executing it.
If you set a breakpoint on Console.WriteLine("Done awaiting");, you will see that it runs on the main thread within the WaitForPendingOperationsToComplete() method.
Additionally, the async/await feature calls the OperationStarted() / OperationCompleted() methods to notify the SynchronizationContext whenever an async void method starts or finishes executing.
The AsyncSynchronizationContext uses these notifications to keep a count of the number of async methods that are running and haven't completed yet. When this count reaches zero, the WaitForPendingOperationsToComplete() method stops running the message loop, and the control flow returns to the caller.
To view this process in the debugger, set breakpoints in the Post, OperationStarted and OperationCompleted methods of the synchronization context. Then step through the AsyncMethod call:
When AsyncMethod is called, .NET first calls OperationStarted()
This sets the _operationCount to 1.
Then the body of AsyncMethod starts running (and starts the background task)
At the await statement, AsyncMethod yields control as the task is not yet complete
currentContext.WaitForPendingOperationsToComplete(); gets called
No operations are available in the queue yet, so the main thread goes to sleep at _operationsAvailable.WaitOne();
On the background thread:
at some point the task finishes sleeping
Output: Done sleeping!
the delegate finishes execution and the task gets marked as complete
the Post() method gets called, enqueuing a continuation that represents the remainder of the AsyncMethod
The main thread wakes up because the queue is no longer empty
The message loop runs the continuation, thus resuming execution of AsyncMethod
Output: Done awaiting
AsyncMethod finishes execution, causing .NET to call OperationComplete()
the _operationCount is decremented to 0, which marks the message loop as complete
Control returns to the message loop
The message loop finishes because it was marked as complete, and WaitForPendingOperationsToComplete returns to the caller
Output: Press any key to continue. . .
I have a class that is a "manager" sort of class. One of it's functions is to signal that the long running process of the class should shut down. It does this by setting a boolean called "IsStopping" in class.
public class Foo
{
bool isStoping
void DoWork() {
while (!isStopping)
{
// do work...
}
}
}
Now, DoWork() was a gigantic function, and I decided to refactor it out and as part of the process broke some of it into other classes. The problem is, Some of these classes also have long running functions that need to check if isStopping is true.
public class Foo
{
bool isStoping
void DoWork() {
while (!isStopping)
{
MoreWork mw = new MoreWork()
mw.DoMoreWork() // possibly long running
// do work...
}
}
}
What are my options here?
I have considered passing isStopping by reference, which I don't really like because it requires there to be an outside object. I would prefer to make the additional classes as stand alone and dependancy free as possible.
I have also considered making isStopping a property, and then then having it call an event that the inner classes could be subscribed to, but this seems overly complex.
Another option was to create a "Process Cancelation Token" class, similar to what .net 4 Tasks use, then that token be passed to those classes.
How have you handled this situation?
EDIT:
Also consider that MoreWork might have a EvenMoreWork object that it instantiates and calls a potentially long running method on... and so on. I guess what i'm looking for is a way to be able to signal an arbitrary number of objects down a call tree to tell them to stop what they're doing and clean up and return.
EDIT2:
Thanks for the responses so far. Seems like there's no real consensus on methods to use, and everyone has a different opinion. Seems like this should be a design pattern...
You can go two ways here:
1) The solution you've already outlined: pass a signaling mechanism to your subordinate objects: a bool (by ref), the parent object itself cloaked in an interface (Foo: IController in the example below), or something else. The child objects check the signal as needed.
// Either in the MoreWork constructor
public MoreWork(IController controller) {
this.controller = controller;
}
// Or in DoMoreWork, depending on your preferences
public void DoMoreWork(IController controller) {
do {
// More work here
} while (!controller.IsStopping);
}
2) Turn it around and use the observer pattern - which will let you decouple your subordinate objects from the parent. If I were doing it by hand (instead of using events), I'd modify my subordinate classes to implement an IStoppable interface, and make my manager class tell them when to stop:
public interface IStoppable {
void Stop();
}
public class MoreWork: IStoppable {
bool isStopping = false;
public void Stop() { isStopping = true; }
public void DoMoreWork() {
do {
// More work here
} while (!isStopping);
}
}
Foo maintains a list of its stoppables and in its own stop method, stops them all:
public void Stop() {
this.isStopping = true;
foreach(IStoppable stoppable in stoppables) {
stoppable.Stop();
}
}
I think firing an event that your subclasses subscribe to makes sense.
You could create a Cancel() method on your manager class, and on each of your other worker classes. Base it on an interface.
The manager class, or classes that instantiate other worker classes, would have to propagate the Cancel() call to the objects they are composed of.
The deepest nested classes would then just set an internal _isStopping bool to false and your long-running tasks would check for that.
Alternatively, you could maybe create a context of some sort that all the classes know about and where they can check for a canceled flag.
Another option was to create a
"Process Cancelation Token" class,
similar to what .net 4 Tasks use, then
that token be passed to those classes.
I am not familiar with this, but if it is basically an object with a bool property flag, and that you pass into each class, then this seems like the cleanest way to me. Then you could make an abstract base class that has a constructor that takes this in and sets it to a private member variable. Then your process loops can just check that for cancellation.
Obviously you will have to keep a reference to this object you have passed into your workers so that it's bool flag can be set on it from your UI.
Your nested types could accept a delegate (or expose an event) to check for a cancel condition. Your manager then supplies a delegate to the nested types that checks its own "shouldStop" boolean. This way, the only dependency is of the ManagerType on the NestedType, which you already had anyway.
class NestedType
{
// note: the argument of Predicate<T> is not used,
// you could create a new delegate type that accepts no arguments
// and returns T
public Predicate<bool> ShouldStop = delegate() { return false; };
public void DoWork()
{
while (!this.ShouldStop(false))
{
// do work here
}
}
}
class ManagerType
{
private bool shouldStop = false;
private bool checkShouldStop(bool ignored)
{
return shouldStop;
}
public void ManageStuff()
{
NestedType nestedType = new NestedType();
nestedType.ShouldStop = checkShouldStop;
nestedType.DoWork();
}
}
You could abstract this behavior into an interface if you really wanted to.
interface IStoppable
{
Predicate<bool> ShouldStop;
}
Also, rather than just check a boolean, you could have the "stop" mechanism be throwing an exception. In the manager's checkShouldStop method, it could simply throw an OperationCanceledException:
class NestedType
{
public MethodInvoker Stop = delegate() { };
public void DoWork()
{
while (true)
{
Stop();
// do work here
}
}
}
class ManagerType
{
private bool shouldStop = false;
private void checkShouldStop()
{
if (this.shouldStop) { throw new OperationCanceledException(); }
}
public void ManageStuff()
{
NestedType nestedType = new NestedType();
nestedType.Stop = checkShouldStop;
nestedType.DoWork();
}
}
I've used this technique before and find it very effective.
Litter your code with statements like this wherever it is most sensible to check the stop flag:
if(isStopping) { throw new OperationCanceledException(); }
Catch OperationCanceledException right at the top level.
There is no real performance penalty for this because (a) it won't happen very often, and (b) when it does happen, it only happens once.
This method also works well in conjunction with a WinForms BackgroundWorker component. The worker will automatically catch a thrown exception in the worker thread and marshal it back to the UI thread. You just have to check the type of the e.Error property, e.g.:
private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
if(e.Error == null) {
// Finished
} else if(e.Error is OperationCanceledException) {
// Cancelled
} else {
// Genuine error - maybe display some UI?
}
}
You can flatten your call stack by turning each DoWork() call into a command using the Command pattern. At the top level, you maintain a queue of commands to perform (or a stack, depending on how your commands interact with each other). "Calling" a function is translated to enqueuing a new command onto the queue. Then, between processing each command, you can check whether or not to cancel. Like:
void DoWork() {
var commands = new Queue<ICommand>();
commands.Enqueue(new MoreWorkCommand());
while (!isStopping && !commands.IsEmpty)
{
commands.Deque().Perform(commands);
}
}
public class MoreWorkCommand : ICommand {
public void Perform(Queue<ICommand> commands) {
commands.Enqueue(new DoMoreWorkCommand());
}
}
Basically, by turning the low-level callstack into a data structure you control, you have the ability to check stuff between each "call", pause, resume, cancel, etc..