Task chaining without TaskCompletionSource?
Asked Answered
S

3

6

I'm converting some async/await code to chained tasks, so I can use it in the released framework. The await code looks like this

public async Task<TraumMessage> Get() {
  var message = await Invoke("GET");
  var memorized = await message.Memorize();
  return memorized;
}

where

Task<TraumMessage> Invoke(string verb) {}
Task<TraumMessage> Memorize() {}

I was hoping to chain Invoke and Memorize to return the task produced by Memorize, but that results in a Task<Task<TraumMessage>. The solution i've ended up is a TaskCompletionSource<TraumMessage> as my signal:

public Task<TraumMessage> Get() {
  var completion = new TaskCompletionSource<TraumMessage>();
  Invoke("GET").ContinueWith( t1 => {
     if(t1.IsFaulted) {
       completion.SetException(t1.Exception);
       return;
     }
     t1.Result.Memorize().ContinueWith( t2 => {
       if(t2.IsFaulted) {
         completion.SetException(t2.Exception);
         return;
       }
       completion.SetResult(t2.Result);
     });
  });
  return completion.Task;
}

Is there a way to accomplish this without the TaskCompletionSource?

Sturrock answered 30/7, 2011 at 16:29 Comment(0)
H
1

I think that's pretty much the only way to accomplish what you want. Chaining disparate Tasks together isn't supported by the continuation APIs, so you have to resort to using a TaskCompletionSource like you have to coordinate the work.

I don't have the Async CTP installed on this machine, but why don't you take a look at the code with a decompiler (or ILDASM if you know how to read IL) to see what it's doing. I bet it does something very similar to your TCS code under the covers.

Hiro answered 30/7, 2011 at 17:10 Comment(3)
Thanks to Jon Skeet's EduAsync series, I have looked under the hood of async/await and it basically builds a class that runs a state machine. I've used that approach as well before, but it's even more tedious than the TCS approach.Sturrock
Ah, well, I thought about this a bunch more and there's just no way with pure continuations unless you want to block the continuation of the 1st task waiting on the result of the continuation of the 2nd task. If the stars were aligned work stealing could mean there's very little overhead in that, but since you theoretically have no clue how the 2nd task is created (could be a totally diff. task scheduler or APM for example) there's no guarantees for that. So you have to assume you'd be blocking a thread waiting on the result. If u want to see what I mean let me know and I'll update my answer.Hiro
At blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx Stephen Taub wraps the 'TaskCompletionSource' in a helper method called 'Then' to implement this pattern.Quartile
B
4

Yes, the framework comes with a handy Unwrap() extension method for exactly what you want.

Invoke("GET").ContinueWith( t => t.Result.Memorize() ).Unwrap();

If you're doing cancellation then you'll need to pass cancel tokens into the appropriate places, obviously.

Broiler answered 25/4, 2012 at 13:20 Comment(0)
H
1

I think that's pretty much the only way to accomplish what you want. Chaining disparate Tasks together isn't supported by the continuation APIs, so you have to resort to using a TaskCompletionSource like you have to coordinate the work.

I don't have the Async CTP installed on this machine, but why don't you take a look at the code with a decompiler (or ILDASM if you know how to read IL) to see what it's doing. I bet it does something very similar to your TCS code under the covers.

Hiro answered 30/7, 2011 at 17:10 Comment(3)
Thanks to Jon Skeet's EduAsync series, I have looked under the hood of async/await and it basically builds a class that runs a state machine. I've used that approach as well before, but it's even more tedious than the TCS approach.Sturrock
Ah, well, I thought about this a bunch more and there's just no way with pure continuations unless you want to block the continuation of the 1st task waiting on the result of the continuation of the 2nd task. If the stars were aligned work stealing could mean there's very little overhead in that, but since you theoretically have no clue how the 2nd task is created (could be a totally diff. task scheduler or APM for example) there's no guarantees for that. So you have to assume you'd be blocking a thread waiting on the result. If u want to see what I mean let me know and I'll update my answer.Hiro
At blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx Stephen Taub wraps the 'TaskCompletionSource' in a helper method called 'Then' to implement this pattern.Quartile
G
0

You can use attached child tasks. The parent task will only transition into the completed status when all child tasks are complete. Exceptions are propagated to the parent task. You will need a result holder, as the result will be assigned after the parent task's delegate has finished, but will be set when the parent tasks continuations are run.

Like this:

public class Holder<T> where T: class
{
   public T Value { get; set; }
}

public Task<Holder<TraumMessage>> Get() {
  var invokeTask = Invoke("GET");
  var result = invokeTask.ContinueWith<Holder<TraumMessage>>(t1 => {
    var holder = new Holder<TraumMessage>();
    var memorizeTask = t1.Result.Memorize();
    memorizeTask.ContinueWith(t2 => {
      holder.Value = t2.Result;
    }, TaskContinuationOptions.AttachedToParent);
    return holder;
  });
  return result;
}
Grainfield answered 4/9, 2011 at 4:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.