Continuation doesn't run if previous is Canceled - c#

Hopefully, this is a simple question. I have this line of code:
Task operation = Task.Factory.StartNew(() => this.Start(arg), m_token.Token)
.ContinueWith((previous) => Completed(previous, arg), TaskScheduler.FromCurrentSynchronizationContext());
The arg object contains the CancellationToken.
If I cancel the task (which I confirmed that it is cancelled) the Completed method is not called at all. Not sure what is happening here and what did I do wrong.
Perhaps this is important bit: I'm using Enterprise library Exception handling block which wraps all exceptions. However everything works just fine when Start throws exception; the Completed is called.

The returned Task will not be scheduled for execution until the
current task has completed. If the criteria specified through the
continuationOptions parameter are not met, the continuation task will
be canceled instead of scheduled.
See details here.

Related

Changing some Dispatcher.BeginInvoke functionality into that of Dispatcher.Invoke

The reason for what I'm going to ask here is that Dispatcher.Invoke throws a TaskCanceledException when another thread executes Dispatcher.InvokeShutdown(). Dispatcher.BeginInvoke() does not seem to suffer from this and I wanted to move my codebase from using Dispatcher.Invoke(...) to Dispatcher.BeginInvoke(...). And below I wanted to ask if the following two Work() methods both running on a separate background thread are equivalent? (do any of you see any issues changing the first into the second?):
Work(){
Dispatcher.Invoke(() => {sameFunction()});
//more work
...
}
Work(){
var task = Dispatcher.BeginInvoke((Action)(() => {sameFunction()});
task.Wait();
//more work
...
}
This issue is a direct consequence of the following issue's answer not having functioned as hoped for. It seems that once Dispatcher.InvokeShutdown has been called (once Dispatcher.HasShutdownStarted is true), all calls to Dispatcher.Invoke will end in throwing a TaskCancelledException.
You should use Dispatcher.InvokeAsync instead of Dispatcher.BeginInvoke. BeginInvoke is part of the old API.
Also, never call Wait(), but use await:
await Dispatcher.InvokeAsync()
Using the new API also allows you to cancel operations (Invoke and InvokeAsync only): to fix the exception issue, you should provide a shared CancellationToken, that you associate with the Dispatcher (virtually, with the one you expect to get shutdown), to every invocation.
This way you can cancel the pending and running operations gracefully before you shutdown the Dispatcher.
Dispatcher.InvokeShutdown will abort any running Dispatcher operation, which applies to every synchronous Dispatcher.Invoke execution - hence the TaskCanceledException is being thrown on abortion.
Regarding Dispatcher.InvokeAsync (and Dispatcher.BeginInvoke): your probably don't experience the cancellation exception, because the dispatched operation is still pending due to its asynchronous execution.
This leads to the difference of both Dispatcher invocation examples:
Your first example uses Dispatcher.Invoke. From the documentation you could learn that it executes synchronously. This means, the delegate is executed immediately (pushed to the front of the queue - respecting the assigned priorities).
The second example uses Dispatcher.BegingInvoke (same applies to the modern Dispatcher.InvokeAsync). This method invokes the delegate asynchronously. This means, the delegate is enqueued into the dispatcher queue and executed after all preceeding pending operations in this queue are completed. If you don't await the call, the Dispatcher returns immediately after enqueuing the delegate and execution continues (this is the asynchronous part).
Therfore, the examples are not equivalent. You have to decide if postponing the delegate's execution is reasonable (Dispatcher.InvokeAsync) or not (Dispatcher.Invoke).

How to handle tasks that are canceled without triggering a cancellation itself?

I'm trying to get tasks in C# to work for a specific use case but I'm not understanding how task continuation options affect the flow of tasks.
What I'm trying to do is get a series of tasks chained together with ContinueWith. This will look something like this:
A -> B -> C -> D
However, I want to include the option to short-circuit this in the event an error, so it should look like this:
A -> B -> C -> D -> X
So I put "OnlyOnRanToCompletion" as the task continuation option for each of the ContinueWith functions. Then, to catch the cancellation and return an error, I put a final task at the end of the chain with the task continuation option set to "OnlyOnCanceled".
The problem is that when this last block is hit, the continuation option is not met and the task then gets set to cancelled even if the original series of tasks was never cancelled.
What I want to happen is have A through D run, and if one of them results in a cancellation, skip the rest and run X. If A through D complete, the task shouldn't cancel. The solution needs to support an arbitrary number of continuations and will be created using LINQ.Expressions, so using async/await is probably not going to fly unless it's done creatively.
Some sample code that exhibits this is:
var cts = new CancellationTokenSource();
var token = cts.Token;
var t = Task.FromResult(1)
.ContinueWith(
x => x.Result + 1,
token,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default)
.ContinueWith(
x => x.Result + 1,
token,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default)
.ContinueWith(
x => -1,
token,
TaskContinuationOptions.OnlyOnCanceled,
TaskScheduler.Default);
The expected behavior here would be to return 3, and the status not completed.
The actual result is that the task is cancelled.
How do I do this?
Also, I can't use async because my goal is to piggyback off TPL inside of something compiled from LINQ.Expressions so that it can evaluate asynchronously and handle errors at the end without throwing any exceptions.
Figured it out - to get the last continuation to run regardless of whether or not the previous continuations completed and without setting the status to canceled do this:
Change the continuation option of the last continuation to TaskContinuation.None so that it always runs, so it won't cancel if it gets here with a status of completed.
Don't pass in a cancellation token to the last continuation because passing in a cancellation token that has been cancelled seems to have the effect of causing the continuation to cancel if it would have otherwise run without the token.
See the remarks for ContinueWith for an explanation of this behavior:
The returned Task will not be scheduled for execution until the current task has completed. If the criteria specified through the continuationOptions parameter are not met, the continuation task will be canceled instead of scheduled.
Since the criteria for your last ContinueWith call weren't met, the Task returned from that call was cancelled.

Task doesn't Go to Faulted State If ConfigurAwait set to False

So here is what I'm trying to achieve. I launch a task and don't do wait/result on it. To ensure that if launched task goes to faulted state (for e.g. say throw an exception) I crash the process by calling Environment FailFast in Continuation.
How the problem I'm facing is that If I ran below code, Inside ContinueWith, the status of the task (which threw exception) shows up as "RanToCompletion". I expected it to be Faulted State.
private Task KickOfTaskWorkAsync()
{
var createdTask = Task.Run(() => this.RunTestTaskAsync(CancellationToken.None).ConfigureAwait(false), CancellationToken.None);
createdTask.ContinueWith(
task => Console.WriteLine("Task State In Continue with => {0}", task.Status));
return createdTask;
}
private async Task RunTestTaskAsync(CancellationToken cancellationToken)
{
throw new Exception("CrashingRoutine: Crashing by Design");
}
This is really strange :( If I remove the 'ConfigureAwait(false)' inside Task.Run function call, the task does goes to Faulted state inside Continue with. Really at loss to explain what's going on and would appreciate some help from community.
[Update]: My colleague pointed out an obvious error. I am using ConfigureAwait while I make a call to RunTestAsync inside Test.Run even though I don't await it. In this case, ConfigureAwait doesn't return a Task to Task.Run. If I don't call ConfigureAwait, a Task does get returned and things work as expected.
Your error is a specific example of a broader category of mistake: you are not observing the Task you actually care about.
In your code example, the RunTestTaskAsync() returns a Task object. It completes synchronously (because there's no await), so the Task object it returns is already faulted when the method returns, due to the exception. Your code then calls ConfigureAwait() on this faulted Task object.
But all of this happens inside another Task, i.e. the one that you start when you call Task.Run(). This Task doesn't do anything to observe the exception, so it completes normally.
The reason you observe the exception when you remove the ConfigureAwait() call has nothing to do with the call itself. If you left the call and passed true instead, you would still fail to observe the exception. The reason you can observe the exception when you remove the call is that, without the call to ConfigureAwait(), the return value of the lambda expression is a Task, and this calls a different overload of Task.Run().
This overload is a bit different from the others. From the documentation:
Queues the specified work to run on the thread pool and returns a proxy for the task returned by function.
That is, while it still starts a new Task, the Task object it returns represents not that Task, but the one returned by your lambda expression. And that proxy takes on the same state as the Task it wraps, so you see it in the Faulted state.
Based on the code you posted, I would say that you shouldn't be calling Task.Run() in the first place. The following will work just as well, without the overhead and complication of the proxy:
static void Main(string[] args)
{
Task createdTask = RunTestTaskAsync();
createdTask.ConfigureAwait(false);
createdTask.ContinueWith(
task => Console.WriteLine("Task State In Continue with => {0}", task.Status)).Wait();
}
private static async Task RunTestTaskAsync()
{
throw new Exception("CrashingRoutine: Crashing by Design");
}
(I removed the CancellationToken values, because they have nothing at all to do with your question and are completely superfluous here.)

Method (Action) call in Task.Factory.StartNew not being invoked immediately

I was under the impression that I could just make this thread call and whatever was in my method "DoSomething" would just start happening, but apparently not.
When I invoke this line:
Task.Factory.StartNew(() =>
ControllerClass.DoSomething("data"),
CancellationToken.None,
TaskCreationOptions.LongRunning, TaskScheduler.Default);
ControllerClass.DoSomething("data") is not executed.
However, if I add a Wait, then the method gets called.
The reason I'm using the LongRunning option is that the method can be LongRunning in certain things aren't in place when it start executing. And yes, the the method itself works when called inline. It is just that it needs to be in a thread so that the main program can continue while this thread does its thing.
By the way, I have also tried this way to call it and same results:
Task.Factory.StartNew(() =>
ControllerClass.DoSomething("data")).ContinueWith
(t =>
{
SendErrorEmail(t.Exception);
}, TaskContinuationOptions.OnlyOnFaulted
);
Am I missing some option to tell it to start executing the method call right away?
I was under the impression that I could just make this thread call and
whatever was in my method "DoSomething" would just start happening,
but apparently not.
No, this is not happening. Actually, when you write this:
Task.Factory.StartNew(() =>
ControllerClass.DoSomething("data"),
CancellationToken.None,
TaskCreationOptions.LongRunning, TaskScheduler.Default);
under the cover your task is get into a queue and sooner or later will be run on a thread of the ThreadPool.
According to MSDN:
Calling StartNew is functionally equivalent to creating a
Task using one of its constructors and then calling Start to
schedule it for execution.
Ragarding you other statement:
However, if I add a Wait, then the method gets called.
This is true, because the TaskFactory.StartNew returns a Task object. when we call the Wait method of a task
If the current task has not started execution, the Wait method
attempts to remove the task from the scheduler and execute it inline
on the current thread. If it is unable to do that, or if the current
task has already started execution, it blocks the calling thread until
the task completes.
In a few words Wait is a blocking action.
Please have a look here for more information on this.
Am I missing some option to tell it to start executing the method call
right away?
No. Unless calling wait there isn't any alternative, as far as I am aware of.

Who canceled my Task?

My C# Task is getting canceled, but not by me. I don't get a stacktrace and I can't figure out where the problem occurs.
My task invocation looks like this:
var t = Task<Boolean>.Factory.StartNew(() =>
{
Boolean bOk = DoSomthingImportant();
return bOk;
}, TaskCreationOptions.AttachedToParent)
.ContinueWith<Boolean>((theTask) =>
{
var reason = theTask.IsCanceled ? "it was canceled" : "it faulted";
Debug.WriteLine("Error: Task ended because " + reason + ".");
... log the exception to one of my objects...
return false;
}, TaskContinuationOptions.NotOnRanToCompletion);
I want the continuation task to run if the task faulted or was canceled, but not if it ran okay.
The continuation is never executed.
Later on my program catches an AggregateException which is wrapping a TaskCanceledException.
My other major interaction with my tasks is to call WaitAny(taskArray, timeout) until I have no more tasks to start, then call WaitAll with no timeout until the last task is done.
Could WaitAny with a timeout cause a cancellation? Why didn't my continuation get called?
This is only my second brush with the Task library, so I am clueless.
UPDATE:
I found this SO question: How to propagate a Task's Canceled status to a continuation task.
One error in my code above (but not the cause of the Cancelation) is that I assumed that the Continuation tasks status was the same as the original task's status. In fact you have to do some work to get the one from the other, as the other post describes.
UPDATE 2:
Brian: Thanks for the documentaion reference. I had searched high and low for alternate causes of a Task being canceled, but missed these words:
"If you are waiting on a Task that transitions to the Canceled state,
a Task (wrapped in an AggregateException) is manufactured and thrown.
Note that this exception indicates successful cancellation instead of
a faulty situation. Therefore, the Task's Exception property returns
null."
You're waiting on the continuation and since the original task ran to completion the continuation task was cancelled. This behavior is covered in the documentation.

Categories

Resources