0

In the docs for TPL I found this line:

Invoke multiple continuations from the same antecedent

But this isn't explained any further. I naively assumed you could chain ContinueWiths in a pattern matching like manner until you hit the right TaskContinuationOptions.

TaskThatReturnsString()
    .ContinueWith((s) => Console.Out.WriteLine(s.Result), TaskContinuationOptions.OnlyOnRanToCompletion)
    .ContinueWith((f) => Console.Out.WriteLine(f.Exception.Message), TaskContinuationOptions.OnlyOnFaulted)
    .ContinueWith((f) => Console.Out.WriteLine("Cancelled"), TaskContinuationOptions.OnlyOnCanceled)
    .Wait();

But this doesn't work like I hoped for at least two reasons.

  • The continuations are properly chained so the 2nd ContinueWith gets the result form the 1st, that is implemented as new Task, basically the ContinueWith task itself. I realize that the String could be returned onwards, but won't that be a new task with other info lost?
  • Since the first option is not met, the Task is just cancelled. Meaning that the second set will never be met and the exceptions are lost.

So what do they mean in the docs when they say multiple continuations from the same antecedent? Is there a proper patter for this or do we just have to wrap the calls in try catch blocks?


EDIT

So I guess this was what I was hoping I could do, note this is a simplified example.

    public void ProccessAllTheThings()
    {
        var theThings = util.GetAllTheThings();
        var tasks = new List<Task>();
        foreach (var thing in theThings)
        {
            var task = util.Process(thing)
                        .ContinueWith((t) => Console.Out.WriteLine($"Finished processing {thing.ThingId} with result {t.Result}"), TaskContinuationOptions.OnlyOnRanToCompletion)
                        .ContinueWith((t) => Console.Out.WriteLine($"Error on processing {thing.ThingId} with error {t.Exception.Message}"), TaskContinuationOptions.OnlyOnFaulted);
            tasks.Add(task);
        }
        Task.WaitAll(tasks.ToArray());
    }

Since this wasn't possible I was thinking I would have to wrap each task call in a try catch inside the loop so I wouldn't stop the process but not wait on it there. I wasn't sure what the correct way.

Sometimes a solution is just staring you in the face, this would work wouldn't it?

    public void ProccessAllTheThings()
    {
        var theThings = util.GetAllTheThings();
        var tasks = new List<Task>();
        foreach (var thing in theThings)
        {
            var task = util.Process(thing)
                .ContinueWith((t) =>
                {
                    if (t.Status == TaskStatus.RanToCompletion)
                    {
                        Console.Out.WriteLine($"Finished processing {thing.ThingId} with result {t.Result}");
                    }
                    else
                    {
                        Console.Out.WriteLine($"Error on processing {thing.ThingId} - {t.Exception.Message}");
                    }
                });
            tasks.Add(task);
        }
        Task.WaitAll(tasks.ToArray());
    }
Ingó Vals
  • 4,788
  • 14
  • 65
  • 113
  • 1
    If you want to create pipelines of processing blocks use the TPL Dataflow library. Don't try to build this yourself. – Panagiotis Kanavos Dec 03 '18 at 12:10
  • 1
    It is easier to just await the task and handle potential exceptions with try/catch – FCin Dec 03 '18 at 12:10
  • @PanagiotisKanavos I wasn't aware of this library. I will take a look but I guess it's overkill for what use cases I was envisioning. – Ingó Vals Dec 03 '18 at 13:34
  • 1
    Just use `await` instead of manually adding continuations. The better error handling semantics is one of the many major advantages of using it. – Servy Dec 03 '18 at 15:19
  • @Servy What if it isn't a Async method? To be fair I could just turn it into a Async method of course. I guess I'm being a bit daft, what happens if I `Await` inside a loop. does the loop keep on iterating? If it does, then of course that would work fine. – Ingó Vals Dec 03 '18 at 15:29
  • @IngóVals If portions of the containing method need to run before that operation finishes then you'll simply need to create a new method to be the one that adds your continuations. – Servy Dec 03 '18 at 15:37

1 Answers1

0

What you did is to create a sequential chain of multiple tasks.

What you need to do is attach all your continuation tasks to the first one:

var firstTask = TaskThatReturnsString();
var t1 = firstTask.ContinueWith (…);
var t2 = firstTask.ContinueWith (…);
var t3 = firstTask.ContinueWith (…);

Then you need to wait for all the continuation tasks:

Task.WaitAll (t1, t2, t3);
Nick
  • 4,787
  • 2
  • 18
  • 24
  • This will result in me having 3 tasks, two of which will throw a `TaskCanceledException`. I'm hoping to just handle a faulted task without it causing further exceptions. I guess ContinueWith isn't meant for this and you should always just wrap in a try catch / or return a resultObject if approppriate. – Ingó Vals Dec 03 '18 at 13:15
  • Well, obviously your question is not clear. Please rephrase it to explain in details what you want to achieve. – Nick Dec 03 '18 at 13:26
  • Fair enough, I only implied what I was after, and what you suggest here is certainly a solution in some form. But you always end up having to wrap in a try catch, which I was hoping you could go without if my exception handling was simple enough, – Ingó Vals Dec 03 '18 at 13:32