Welcome
Ladies and Gents:

These forums are now closed and registration disabled.

Please join us at our new forum on Proboards. Our hope is that these new forums are more stable, provide more and better features, and allow continuation of the project forums in a safer, more secure, long term environment.

me3explorer.proboards.com

--The ME3Explorer Team

NET 4.0 Tasks and Continuation Options

Coder-centric area for programming advice and questions.

NET 4.0 Tasks and Continuation Options

Postby Fog.Gene » 12 May 2013, 14:11

I'm leaving this here as a piece of knowledge, in the event that anybody might benefit from it in the future. This is not a tutorial, just a piece of info with the outcome of a successfully wasted afternoon. I'll assume the reader knows about Tasks and parallel programming in NET 4.

With Net 4.0 came a nice form of threading in the form of Tasks, and with them the ability to set continuations. Hence, my goal this afternoon was to have a particular Task run a piece of code asynchronously and, when finished, branch into two possible continuations depending on if it ran to completion or was cancelled (using a CancellationTokenSource and tying it to the task). For a proof of principle experiment, the initial code may look like this:

CancellationTokenSource cts = new CancellationTokenSource();

Task t = Task.Factory.StartNew((l) =>
{
Thread.Sleep(5000);

if (cts.IsCancellationRequested)
{
throw new OperationCanceledException(cts.Token);
}

}, null, cts.Token);

t.ContinueWith((l) =>
{
DebugOutput.PrintLn("I was cancelled");

}, TaskContinuationOptions.OnlyOnCanceled);

t.ContinueWith((l) =>
{
DebugOutput.PrintLn("I was completed");

}, TaskContinuationOptions.OnlyOnRanToCompletion);


This works perfectly, save that it throws an unhandled exception. What I found out is that apparently there is no way in hell I can handle that exception and also have the Task set as cancelled. This means that only one of the continuation tasks will run, the second one with the option 'OnlyOnRanToCompletion'.

Now the solution to this might seem dead easy to you, and it really is, but I was dead set on trying to use the default TaskContinuationOptions. Because well, they are there to use, right? Nope, not for what I wanted. To make it work in a similar fashion to what I was expecting, only a little tweak was needed:

CancellationTokenSource cts = new CancellationTokenSource();

Task t = Task.Factory.StartNew(() =>
{
Thread.Sleep(5000);
});

t.ContinueWith((l) =>
{
if (cts.IsCancellationRequested)
{
DebugOutput.PrintLn("I was cancelled");
}
else
{
DebugOutput.PrintLn("I was completed");
}

});


That's it. Tying the token to the task: not needed. Use the cancellation options, nope. If I want to have the outcome to branch out in a continuation task, just use one continuation task and check for the same cancellation token in an if statement. Global or local vars or TaskCompletionSource results won't work because the continuation task code is inside an anonymous lambda and won't see any outside declaration except the tokensource (at least I could not find a way for it to see anything else).

I know there is a point for all these options (fault, cancel, etc...), but it seems such a waste that the parent Task status won't change when using a catch or some sort of handle. Anyway, that's how I'm doing it now, until the next hurdle...

Hope this helps someone in the future.
Regere: Let the world know of its sovereign. I shall be the rule by which all things are measured.
Polyhistor: Then I am to be the slave, for all things shall be my master.
Fog.Gene
Emeritus
 
Posts: 259
Joined: 20 Feb 2013, 05:09
Has thanked: 119 time
Have thanks: 65 time

Re: NET 4.0 Tasks and Continuation Options

Postby mnn » 15 May 2013, 19:03

Well, I've scratched only surface of .NET 4.0 Tasks (so far I haven't used any continuations), but cancellation worked well. I had some minor issues, while using coroutines in Caliburn.Micro, but everything works as expected now.

And I'm using this to check, if Task was canceled:

Code: Select all
public void TaskMethod(CancellationToken cancellationToken)
{
    // ...
    for (...) // some long loading
    {
        cancellationToken.ThrowIfCancellationRequested();
    }
    // ...
}

while Caliburn.Micro is simply using ContinueWith without TaskContinuationOptions:

Code: Select all
// somewhere
// Task innerTask = ...
innerTask.ContinueWith(OnCompleted, TaskScheduler.FromCurrentSynchronizationContext());

protected virtual void OnCompleted(Task task) {
    Completed(this, new ResultCompletionEventArgs {WasCancelled = task.IsCanceled, Error = task.Exception});
}
mnn
User
 
Posts: 42
Joined: 22 Aug 2012, 17:10
Has thanked: 1 time
Have thanks: 15 time

Re: NET 4.0 Tasks and Continuation Options

Postby Fog.Gene » 16 May 2013, 10:45

Yep, that's it. As long as you don't handle the exception the task is set as cancelled. Otherwise it will be considered completed, regardless of the state of the token, and will continue through that branch. It looks like Caliburn works the same way.

There is only one situation that I found where no exception has to be thrown, which is requesting cancellation (via its token) before the task is started. The task.IsCancelled will return true and will proceeded with the appropriate continuation.

I know this is not really ME3 programming, but I was so frustrated trying to make it work that way... I had to post it if only to save somebody else's time when trying the same.

Edit: Incidentally, NET 4.5 Async/Await looks pretty awesome. Too bad we can't use it in ME3Ex... not without downloading extra packages anyway.
Regere: Let the world know of its sovereign. I shall be the rule by which all things are measured.
Polyhistor: Then I am to be the slave, for all things shall be my master.
Fog.Gene
Emeritus
 
Posts: 259
Joined: 20 Feb 2013, 05:09
Has thanked: 119 time
Have thanks: 65 time

Re: NET 4.0 Tasks and Continuation Options

Postby mnn » 17 May 2013, 13:37

Yeah, but you could handle OperationCanceledException this way:

Code: Select all
void TaskMethod(CancellationToken cancellationToken, /* other task method parameters */)
{
    try
    {
        // task code
        cancellationToken.ThrowIfCancellationRequested();
        // ..
    }
    catch (OperationCanceledException)
    {
        throw;  // rethrow exception to upper level (if TaskMethod is called by Task runtime (or indirectly, like through lambda expression) then task should get cancelled properly
    }
    catch (Exception ex)
    {
        // handle exception
        // like log the error
        Debug.WriteLine(ex);
       
        // then rethrow, if you want exception to propagate to upper level (task runtime, or parent task:
        // http://msdn.microsoft.com/en-us/library/ee372288.aspx)
        throw;
    }
}


So when task is canceled, you rethrow the exception, and task should get canceled. Any other exception can be handled and/or passed to parent task.

I haven't tried it, but based on my knowledge this should work.
mnn
User
 
Posts: 42
Joined: 22 Aug 2012, 17:10
Has thanked: 1 time
Have thanks: 15 time

Re: NET 4.0 Tasks and Continuation Options

Postby Fog.Gene » 17 May 2013, 13:45

I think there was a little misunderstanding with what I said (improper use of the word "handle" on my part). Handling the exception works as you have posted, what I mean't is that rethrowing has to happen or the Task won't be cancelled. So yes, it can be handled (e.g. to have a log like you have said), but needs to be rethrown.

My problem with the whole idea was that I thought using a cancellation token would be an elegant way of cancelling a Task (and change its status to IsCancelled), without having to throw an exception in the middle of the application (and subsequent exit). I still struggle trying to accept the concept of having a cancellation token, an IsCancelled status and an OnCancelled continuation, and then make it behave the same as IsFaulted.

I guess the logic is that this is not a user comodity (e.g. having a cancellation button), but rather a developer tool to cancel a Task when shit happens, have it handled and then exception->exit.
Regere: Let the world know of its sovereign. I shall be the rule by which all things are measured.
Polyhistor: Then I am to be the slave, for all things shall be my master.
Fog.Gene
Emeritus
 
Posts: 259
Joined: 20 Feb 2013, 05:09
Has thanked: 119 time
Have thanks: 65 time

Re: NET 4.0 Tasks and Continuation Options

Postby mnn » 17 May 2013, 19:14

Fog.Gene wrote:I guess the logic is that this is not a user comodity (e.g. having a cancellation button), but rather a developer tool to cancel a Task when shit happens, have it handled and then exception->exit.

Not sure about that. I've read some stuff about Tasks in C# 5.0 in Nutshell book and from what I remember, it said that CancellationTokens are used in this way.

Anyway, here on MSDN article it is explicitely said that terminating operation/task can be done in two ways:

- return from delegate (i.e. if (token.IsCancellationRequested) return), but task will transition to RanToCompletion state, instead of Canceled
- throw OperationCanceledException (preferably using ThrowIfCancellationRequested method of CancellationToken)

TaskStatus.Canceled definition:
The task acknowledged cancellation by throwing an OperationCanceledException with its own CancellationToken while the token was in signaled state, or the task's CancellationToken was already signaled before the task started executing. For more information, see Task Cancellation.


This article shows how to cancel task and its children.


Fog.Gene wrote:This works perfectly, save that it throws an unhandled exception. What I found out is that apparently there is no way in hell I can handle that exception and also have the Task set as cancelled. This means that only one of the continuation tasks will run, the second one with the option 'OnlyOnRanToCompletion'.

Thinking about it now, this makes sense. It does throw unhandled exception. The reason is that this exception is expected. Actually, in .NET Framework there are several occurenes of throwing expected exceptions - for instance showing MessageBox from WPF, XML (de)serialization, etc.

This is even noted in MSDN article How to: Cancel a Task and Its Children (take a look at the bottom of the example code):

Code: Select all
static void DoSomeWork(int taskNum, CancellationToken ct)
{
    // Was cancellation already requested?
    if (ct.IsCancellationRequested)
    {
        Console.WriteLine("We were cancelled before we got started.");
        Console.WriteLine("Press Enter to quit.");
        ct.ThrowIfCancellationRequested();
    }
    int maxIterations = 1000;

    // NOTE!!! A benign "OperationCanceledException was unhandled
    // by user code" error might be raised here. Press F5 to continue. Or,
    //  to avoid the error, uncheck the "Enable Just My Code"
    // option under Tools > Options > Debugging.
    for (int i = 0; i < maxIterations; i++)
    {
        // Do a bit of work. Not too much.
        var sw = new SpinWait();
        for (int j = 0; j < 3000; j++) sw.SpinOnce();
        Console.WriteLine("...{0} ", taskNum);
        if (ct.IsCancellationRequested)
        {
            Console.WriteLine("bye from {0}.", taskNum);
            Console.WriteLine("\nPress Enter to quit.");

            ct.ThrowIfCancellationRequested();
        }
    }
}


TL;DR:
So basically freaking out about OperationCanceledException is completely meaningless, because Tasks were designed to expect this exception to be thrown on Task cancellation :)

btw. I strongly suggest reading something about Tasks first, instead of "headbutting" it :) Book C# 5.0 in Nutshell is very good, because apart from other .NET Framework stuff and C# language itself, it provides introduction to Threading in .NET Framework and then transitions to .NET 4.0 Tasks. And after that it explains async/await feature of .NET 5.0. It even explains how async/await is implemented in C# 5.0 compiler (which translates async/await to regular code using Tasks).
mnn
User
 
Posts: 42
Joined: 22 Aug 2012, 17:10
Has thanked: 1 time
Have thanks: 15 time

Re: NET 4.0 Tasks and Continuation Options

Postby Fog.Gene » 18 May 2013, 04:48

I have that book, it's freaking awesome. That's the guide I used to start with Tasks (pretty much anything I want to do I read it there first), and yes it would be nice if I could practice async/await, but ME3Explorer doesn't have the blc async package for NET4.5. I indeed read the MSDN articles extensively before implementing Tasks, so no headbutting into anything here :)

The exception, benign as it is, is not something you can have in your program as a default cancel Task because in most circumstances the user is likely to report it as a bug or at least freak out. It is a very inelegant way of handling a user cancellation, which is what I wanted in the first place. If the user clicks on the cancel button, the last thing she wants to see is an unhandled message exception. That'd freak the hell out of anybody the first time. I know because I'm a user.

I also read SO and answers to my same question. Most people responses were in the same tune as your last post, "it is expected", "embrace the exception" and other "deal with it" type of answers. No offense intended, but in my opinion all those answers don't deal with the main topic of the question. A lot of developers unfamiliar with Tasks, even if we read a lot about them beforehand, approach Cancellation tokens, Tasks and OnCancelled continuation with the idea that we can use it as a method to approach user-cancellation. It is not the case, for the reasons I stated in the paragraph above.

That was the point behind this post. I wasted an afternoon trying to make it work that way, and it turns out it is not possible. None of the sources (book and msdn) or answers (SO mainly) I read are explicit about this. They do imply you can use it this way, but I'd like to see a program where when I cancel a task, I get a "benign" unhandled exception and everything is fine and dandy. Simply not the best way to approach it. Hence I posted an alternative solution, which is the one I use and it works like a charm :)

Hope it makes more sense now.

Edit: I always read the whole thing mate, no need for tl;dr.
Regere: Let the world know of its sovereign. I shall be the rule by which all things are measured.
Polyhistor: Then I am to be the slave, for all things shall be my master.
Fog.Gene
Emeritus
 
Posts: 259
Joined: 20 Feb 2013, 05:09
Has thanked: 119 time
Have thanks: 65 time

Re: NET 4.0 Tasks and Continuation Options

Postby mnn » 22 May 2013, 18:12

AFAIK OperationCanceledException is not unhandled - .NET Framework handles it itself.

I know, it's not very neat to raise and handle exception, when the behavior (cancellation) is not "unexpected" (unlike other exceptions). However, as I said, there are a few other exceptions that are raised in .NET Framework, when expected behavior is triggered (MessageBox in WPF, XML (de)serialization and who knows how many else).
mnn
User
 
Posts: 42
Joined: 22 Aug 2012, 17:10
Has thanked: 1 time
Have thanks: 15 time

Re: NET 4.0 Tasks and Continuation Options

Postby Fog.Gene » 23 May 2013, 00:16

It was unhandled in debug mode, in the sense that it throws the ugly exception message at you, unless you mean something else by that. But meh... at least there's other ways.
Regere: Let the world know of its sovereign. I shall be the rule by which all things are measured.
Polyhistor: Then I am to be the slave, for all things shall be my master.
Fog.Gene
Emeritus
 
Posts: 259
Joined: 20 Feb 2013, 05:09
Has thanked: 119 time
Have thanks: 65 time

Re: NET 4.0 Tasks and Continuation Options

Postby mnn » 23 May 2013, 18:36

http://www.codeproject.com/Articles/152765/Task-Parallel-Library-1-of-n#cancelling

It really seems that one should encapsulate Wait/Result/etc. of Task in try/catch, ignore OperationCancelledException and handle the rest.

So basically, you can use regular Task cancellation techniques (CancellationToken(Source), "handle"/ignore OperationCancelledException etc.), or you can create something yourself.

However, if you are actually going to use your own method to cancel Tasks, I recommend documenting/noting it somewhere, in case another .NET developer comes by, so he/she knows what he's looking at (he/she's most likely going to expect regular Task cancellation techniques).

mnn has been thanked by:
mnn
User
 
Posts: 42
Joined: 22 Aug 2012, 17:10
Has thanked: 1 time
Have thanks: 15 time

Next

Return to Coders' Help

Who is online

Users browsing this forum: No registered users and 1 guest

suspicion-preferred