Is this an appropriate way of handling cross-thread operations?
Should I use a new property name, something like "EditValueThreadSafe" instead of overriding "EditValue"? I don't think there is an issue with the changes to the implementation of EditValue, as the base property is called regardless.
namespace MyApplication.Components
{
using System.Windows.Forms;
/// <summary>
/// Thread-safe implementation of the DevExpress.XtraEditors.ComboBoxEdit class.
/// </summary>
public class ComboBoxEditThreadSafe : DevExpress.XtraEditors.ComboBoxEdit
{
/// <summary>
/// Gets or sets the edit value.
/// </summary>
/// <value>The edit value.</value>
public override object EditValue
{
get
{
return base.EditValue;
}
set
{
if (this.InvokeRequired)
{
this.Invoke(new MethodInvoker(delegate
{
this.SetEditValue(value);
}));
}
else
{
this.SetEditValue(value);
}
}
}
/// <summary>
/// Sets the edit value.
/// </summary>
/// <param name="value">The value.</param>
private void SetEditValue(object value)
{
base.EditValue = value;
}
}
}
You can also delegate to another method that does the work, and in that method, if on the wrong thread, (BeginInvoke returns true), then call the same method back again. Doing that that eliminates the need to duplicate code.
public class ComboBoxEditThreadSafe : DevExpress.XtraEditors.ComboBoxEdit
{
public override object EditValue
{
get
{
return base.EditValue;
}
set
{
SetValue(value);
}
}
private void delegate SetValueDlg(object valeu);
private void SetValue(object value)
{
if (this.InvokeRequired)
this.BeginInvoke(
(SetValueDlg)SetValue, // calls itself, but on correct thread
new object[] { value });
else
base.editValue = value;
}
}
You can also use the Action() generic class to eliminate need to create explicit delegate class...
public class ComboBoxEditThreadSafe : DevExpress.XtraEditors.ComboBoxEdit
{
public override object EditValue
{
get { return base.EditValue; }
set { SetValue(value); }
}
private void SetValue(object value)
{
if (this.InvokeRequired)
this.BeginInvoke(
new Action<object>(SetValue), // calls itself, but on correct thread
new object[] { value });
else
base.editValue = value;
}
}
It's thread-safe, yes, though be wary of overriding a property and fundamentally changing the behaviour. Changing the implentation is fine, but this property now behaves very differently, removing the possibility of a specific exception but introducing a possible deadlock or blocking condition, which impacts on the calling code.
So yes, this is the correct use of InvokeRequired & Invoke, but I'd recommend creating a separate, purpose-specific and thread-safe property that is advertised as such.
My UI methods like yours end up looking like this:
public void setStatusLabelText(String s)
{
if (footerStatusLabel.InvokeRequired) {
StringUpdateInvokeDelegate callback = new StringUpdateInvokeDelegate(setStatusLabelText);
this.Invoke(callback, new object[] { s });
}
else {
this.footerStatusLabel.Text = s;
}
}
(this may be old for .net these days - but the point is that you can just do the operation inside this method if you are already on the right thread - makes it a little less irritating to read, but still annoying compared to Java, IMO).
I'll inject my 2 cents here. The actual calls to InvokeRequired/BeginInvoke/Invoke are not entirely thread safe. (see Avoiding the woes of Invoke/BeginInvoke in cross-thread WinForm event handling?) I would recommend finding some way of isolating the calls to these in a single place, utility api, extension method, or the like. In the article above there is complete code for a class that wraps a delegate to provide thread-safe behavior.
Related
Can someone explain whether or not I can call the below class "thread safe"?
As far as I know, we can call something thread safe if we are not breaking existing functionality
Example:
public class BackgroundWorker
{
private readonly IDictionary<string, RunningTask> _runningTasks = new ConcurrentDictionary<string, RunningTask>();
/// <summary>
/// Executes async job for the specified key, only one at a time.
/// </summary>
/// <param name="key"></param>
public void Enqueue(string key)
{
if (_runningTasks.ContainsKey(key))
{
_runningTasks[key].Repeat = true;
return;
}
_runningTasks[key] = new RunningTask();
ExecuteTask(key);
}
private void ExecuteTask(string key)
{
Task.Run(() =>
{
// Do something
if (_runningTasks[key].Repeat)
{
_runningTasks[key].Repeat = false;
ExecuteTask(key);
return;
}
_runningTasks.Remove(key);
});
}
private class RunningTask
{
/// <summary>
/// Flag to repeat a task after completion.
/// </summary>
public bool Repeat { get; set; }
}
}
I don't think so because _runningTasks is shared object and your method Enqueue is writing on this shared object. For example its possible when one thread already executed line number y, another thread will evaluate condition check in line number x as true - which might not be intention.
public void Enqueue(string key)
{
if (_runningTasks.ContainsKey(key)) /*say line no : x */
{
_runningTasks[key].Repeat = true;
return;
}
_runningTasks[key] = new RunningTask(); /*say line no:y*/
ExecuteTask(key);
}
Using ConcurrentDictionary will just ensure no two threads can read/write to/from the dictionary same time.
To your second point :
As far as I know, we can call something thread safe if we are not
breaking existing functionality
No this is not the definition of thread safe (might be ok to say one of desirable outcome in multi threaded environment) I would recommend to read this post for official meaning rather.
I have the following code ...
My Command handler:
public class MyHandler : IHandler
{
// I Want to get rid of this method
public override void ExecuteOperation(BaseOperation operation)
{
// This is a work-around
this.ExecuteOperation(operation as SpecificOperation);
}
public override void ExecuteOperation(SpecificOperation operation)
{
// Do actual work here
}
}
My Command handler dispatcher:
private dynamic FindOperationHandler(TBaseProvisioningOperation operation)
{
... some logic here
return Activator.CreateInstance(handlerType, ... args here ...)
}
My consumer code
public void PerformProvisioningOperation(BaseOperation operation)
{
// Find the correct handler for this operation
var operationHandler = this.FindOperationHandler(operation as TBaseProvisioningOperation);
// make it execute the operation
// NOTE: 'operation' is SpecificOperation type, for example
operationHandler.ExecuteOperation(operation); // <--- problem is here
}
The issue is that when I create an instance of my handler class with the Activator.CreateInstance and pass it a boxed object (i.e. as "BaseOperation") parameter, .NET looks for a method in the handler, which has a parameter of the base type, instead of automatically invoking the one which can handle the object if it were unboxed (i.e. explicitly cast).
Of course we have SpecificOperation : BaseOperation
In other words: I want when I execute operationHandler.ExecuteOperation(operation);, .NET to invoke ExecuteOperation(SpecificOperation operation) instead of ExecuteOperation(BaseOperation operation), because the operation parameter is boxed (i.e. it IS SpecificOperation but is downcast-ed as BaseOperation).
How do I achieve that?
Edit:
public interface IHandler<TOperation> where TOperation : BaseOperation
{
/// <summary>
/// TODO: Get rid of this method
/// </summary>
/// <param name="operation">The operation to execute - boxed</param>
void ExecuteOperation(BaseOperation operation);
/// <summary>
/// Executes the operation
/// </summary>
/// <param name="operation">The operation to execute - unboxed</param>
void ExecuteOperation(TOperation operation);
}
Assuming you're using dynamic here to achieve Double-Dispatch, the problem is that you're casting the wrong object.
It's the operation variable that needs to be casted (in order to defer overload resolution until runtime), not the operationHandler.
Try this instead:
operationHandler.ExecuteOperation(operation as dynamic);
And you can avoid the redundant dynamic definition on your FindOperationHandler:
private IHandler FindOperationHandler(TBaseProvisioningOperation operation)
{
return Activator.CreateInstance(handlerType, ... args here ...) as IHandler;
}
See Double-Dispatch
You should avoid returning dynamic if the complete code is in C#. The Activator.CreateInstance is returning an Object not a dynamic.
Dynamics are for interop between scriptlanguages when the properties/methods aren't strongtyped.
Too bad you didn't described the IHandler interface, but...
I think; The problem you facing is your interface defines that your class must implement the void ExecuteOperation(BaseOperation operation); This way your FindOperationHandler should return an IHandler.
private IHandler FindOperationHandler(TBaseProvisioningOperation operation)
{
... some logic here
return (IHandler)Activator.CreateInstance(handlerType, ... args here ...)
}
And for your handler:
public class MyHandler : IHandler
{
public override void ExecuteOperation(BaseOperation operation)
{
var operation = (SpecificOperation)operation;
// Do actual work here
}
}
I've written class, which is an enumerable wrapper that caches the results of an underlying enumerable, only getting the next element if we enumerate and reach the end of the cached results. It can be multi-threaded (getting the next item in another thread) or single threaded (getting the next item in the current thread).
I'm reading up on unit-testing and would like to get my head around appropriate tests. I'm using nunit. My main issue is that i've already written my class and am using it. It works for what i'm using it for (one thing currently). So, i'm writing my tests by just trying to think of things that could go wrong, which given that i've tested unofficially i'm probably unconsciously writing tests i know i've already checked. How can i get the write balance between too many/fine-grained tests, and too few tests?
Should i only be testing public methods/constructors or should i test every method?
Should i test the CachedStreamingEnumerable.CachedStreamingEnumerator class separately?
Currently i'm only testing when the class is set to be single-threaded. How do i go about testing it when multi-threaded, given that i might need to wait a period of time before an item is retrieved and added to the cache?
What tests am i missing to ensure good coverage? Are any i've already got not needed?
Code for the class, and test class below.
CachedStreamingEnumerable
/// <summary>
/// An enumerable that wraps another enumerable where getting the next item is a costly operation.
/// It keeps a cache of items, getting the next item from the underlying enumerable only if we iterate to the end of the cache.
/// </summary>
/// <typeparam name="T">The type that we're enumerating over.</typeparam>
public class CachedStreamingEnumerable<T> : IEnumerable<T>
{
/// <summary>
/// An enumerator that wraps another enumerator,
/// keeping track of whether we got to the end before disposing.
/// </summary>
public class CachedStreamingEnumerator : IEnumerator<T>
{
public class DisposedEventArgs : EventArgs
{
public bool CompletedEnumeration;
public DisposedEventArgs(bool completedEnumeration)
{
CompletedEnumeration = completedEnumeration;
}
}
private IEnumerator<T> _UnderlyingEnumerator;
private bool _FinishedEnumerating = false;
// An event for when this enumerator is disposed.
public event EventHandler<DisposedEventArgs> Disposed;
public CachedStreamingEnumerator(IEnumerator<T> UnderlyingEnumerator)
{
_UnderlyingEnumerator = UnderlyingEnumerator;
}
public T Current
{
get { return _UnderlyingEnumerator.Current; }
}
public void Dispose()
{
_UnderlyingEnumerator.Dispose();
if (Disposed != null)
Disposed(this, new DisposedEventArgs(_FinishedEnumerating));
}
object System.Collections.IEnumerator.Current
{
get { return _UnderlyingEnumerator.Current; }
}
public bool MoveNext()
{
bool MoveNextResult = _UnderlyingEnumerator.MoveNext();
if (!MoveNextResult)
{
_FinishedEnumerating = true;
}
return MoveNextResult;
}
public void Reset()
{
_FinishedEnumerating = false;
_UnderlyingEnumerator.Reset();
}
}
private bool _MultiThreaded = false;
// The slow enumerator.
private IEnumerator<T> _SourceEnumerator;
// Whether we're currently already getting the next item.
private bool _GettingNextItem = false;
// Whether we've got to the end of the source enumerator.
private bool _EndOfSourceEnumerator = false;
// The list of values we've got so far.
private List<T> _CachedValues = new List<T>();
// An object to lock against, to protect the cached value list.
private object _CachedValuesLock = new object();
// A reset event to indicate whether the cached list is safe, or whether we're currently enumerating over it.
private ManualResetEvent _CachedValuesSafe = new ManualResetEvent(true);
private int _EnumerationCount = 0;
/// <summary>
/// Creates a new instance of CachedStreamingEnumerable.
/// </summary>
/// <param name="Source">The enumerable to wrap.</param>
/// <param name="MultiThreaded">True to load items in another thread, otherwise false.</param>
public CachedStreamingEnumerable(IEnumerable<T> Source, bool MultiThreaded)
{
this._MultiThreaded = MultiThreaded;
if (Source == null)
{
throw new ArgumentNullException("Source");
}
_SourceEnumerator = Source.GetEnumerator();
}
/// <summary>
/// Handler for when the enumerator is disposed.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Enum_Disposed(object sender, CachedStreamingEnumerator.DisposedEventArgs e)
{
// The cached list is now safe (because we've finished enumerating).
lock (_CachedValuesLock)
{
// Reduce our count of (possible) nested enumerations
_EnumerationCount--;
// Pulse the monitor since this could be the last enumeration
Monitor.Pulse(_CachedValuesLock);
}
// If we've got to the end of the enumeration,
// and our underlying enumeration has more elements,
// and we're not getting the next item already
if (e.CompletedEnumeration && !_EndOfSourceEnumerator && !_GettingNextItem)
{
_GettingNextItem = true;
if (_MultiThreaded)
{
ThreadPool.QueueUserWorkItem((Arg) =>
{
AddNextItem();
});
}
else
AddNextItem();
}
}
/// <summary>
/// Adds the next item from the source enumerator to our list of cached values.
/// </summary>
private void AddNextItem()
{
if (_SourceEnumerator.MoveNext())
{
lock (_CachedValuesLock)
{
while (_EnumerationCount != 0)
{
Monitor.Wait(_CachedValuesLock);
}
_CachedValues.Add(_SourceEnumerator.Current);
}
}
else
{
_EndOfSourceEnumerator = true;
}
_GettingNextItem = false;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<T> GetEnumerator()
{
lock (_CachedValuesLock)
{
var Enum = new CachedStreamingEnumerator(_CachedValues.GetEnumerator());
Enum.Disposed += new EventHandler<CachedStreamingEnumerator.DisposedEventArgs>(Enum_Disposed);
_EnumerationCount++;
return Enum;
}
}
}
CachedStreamingEnumerableTests
[TestFixture]
public class CachedStreamingEnumerableTests
{
public bool EnumerationsAreSame<T>(IEnumerable<T> first, IEnumerable<T> second)
{
if (first.Count() != second.Count())
return false;
return !first.Zip(second, (f, s) => !s.Equals(f)).Any(diff => diff);
}
[Test]
public void InstanciatingWithNullParameterThrowsException()
{
Assert.Throws<ArgumentNullException>(() => new CachedStreamingEnumerable<int>(null, false));
}
[Test]
public void SameSequenceAsUnderlyingEnumerationOnceCached()
{
var SourceEnumerable = Enumerable.Range(0, 10);
var CachedEnumerable = new CachedStreamingEnumerable<int>(SourceEnumerable, false);
// Enumerate the cached enumerable completely once for each item, so we ensure we cache all items
foreach (var x in SourceEnumerable)
{
foreach (var i in CachedEnumerable)
{
}
}
Assert.IsTrue(EnumerationsAreSame(Enumerable.Range(0, 10), CachedEnumerable));
}
[Test]
public void CanNestEnumerations()
{
var SourceEnumerable = Enumerable.Range(0, 10).Select(i => (decimal)i);
var CachedEnumerable = new CachedStreamingEnumerable<decimal>(SourceEnumerable, false);
Assert.DoesNotThrow(() =>
{
foreach (var d in CachedEnumerable)
{
foreach (var d2 in CachedEnumerable)
{
}
}
});
}
}
Ad 1)
If you need to test private methods, this should tell you something; probably that your class has too much responsibilities. Quite often, private methods are separate classes waiting to be born :-)
Ad 2)
Yes
Ad 3)
Following the same argument as 1, threading functionality should probably not be done inside the class if it can be avoided. I recall reading something about this in "Clean Code" by Robert Martin. He states something like that threading is a separate concern, that should be separated from other peaces of business logic.
Ad 4)
The private methods are the hardest to cover. Thus, I again turn to my answer 1. If your private methods were public methods in seperate classes, they would be much easier to cover. Also, the test of your main class would be easier to understand.
Regards,
Morten
Rather than riddle you with details, I'd simply advise you to be practical and to follow the "Law of the Critical Few" when creating your tests. You do not need to test every accessor or every small fragment of industry-standard code.
Think of what kinds of things would hurt your class the worst and guard against them. Check for boundary conditions. Use any memories you have as to what may have broken similar code in your past experience. Try test data values that may be unexpected.
You are probably not doing this as an academic exercise. You probably want to ensure that your class is solid and that it will stay that way when you go back later to refactor it or when you want to ensure that it is not the cause of misbehavior in one of its client classes.
Your every test should be there for a reason, not just so you can be cool at the next TDD club meeting!
I made a TV-Player in c# using directshowlib-2005.
now I made a method to search for available channels.
I want this method to run in a different thread so my GUI won't freeze, but I get an error when I try to set the channel in the method. It can't find the IAMTVTuner interface in my graph, altough I know it's there.
If I don't use a different thread, the method works just fine (but my GUI freezes for a while)
I know it has to do something with apartments, but is there a way I can acces that interface in a different thread then the thread where created my graph in?
This problem is because some com classes or interfaces like in the DirectShowLib should be just accessed from the same thread that it was created on.
So the solution to this problem is to implement ISynchronizeInvoke "System.ComponentModel.ISynchronizeInvoke".
For example if you need to access methods in the class named Media that uses internally some classes or methods from the DirectshowLib in multithreading mode, you have to check if invoke required by using InvokeRequired and if true you have to access it via Invoke method.
To demonstrate how to implement ISynchronizeInvoke interface here is a snippet from a code that I develop some time ago in C# 2.0
public abstract class Media : ISynchronizeInvoke
{
//....
private readonly System.Threading.SynchronizationContext _currentContext = System.Threading.SynchronizationContext.Current;
private readonly System.Threading.Thread _mainThread = System.Threading.Thread.CurrentThread;
private readonly object _invokeLocker = new object();
//....
#region ISynchronizeInvoke Members
public bool InvokeRequired
{
get
{
return System.Threading.Thread.CurrentThread.ManagedThreadId != this._mainThread.ManagedThreadId;
}
}
/// <summary>
/// This method is not supported!
/// </summary>
/// <param name="method"></param>
/// <param name="args"></param>
/// <returns></returns>
[Obsolete("This method is not supported!", true)]
public IAsyncResult BeginInvoke(Delegate method, object[] args)
{
throw new NotSupportedException("The method or operation is not implemented.");
}
/// <summary>
/// This method is not supported!
/// </summary>
/// <param name="method"></param>
/// <param name="args"></param>
/// <returns></returns>
[Obsolete("This method is not supported!", true)]
public object EndInvoke(IAsyncResult result)
{
throw new NotSupportedException("The method or operation is not implemented.");
}
public object Invoke(Delegate method, object[] args)
{
if (method == null)
{
throw new ArgumentNullException("method");
}
lock (_invokeLocker)
{
object objectToGet = null;
SendOrPostCallback invoker = new SendOrPostCallback(
delegate(object data)
{
objectToGet = method.DynamicInvoke(args);
});
_currentContext.Send(new SendOrPostCallback(invoker), method.Target);
return objectToGet;
}
}
public object Invoke(Delegate method)
{
return Invoke(method, null);
}
#endregion//ISynchronizeInvoke Members
}
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..