locked
Feedback: TaskCompletionSource<T> used with Task

    Question

  • Using TaskCompletionSource<T> with a Task (non-generic) instance seems confusing from an API standpoint.

    This thread shows it being used with TaskCompletionSource<object>, and returning a Task<object>.  That'll, of course, be usable as a Task (non-generic), but this is not an obvious way of working.  It forces you to set results in odd ways (ie: SetResult(null)).

     

    Much of the API has a non-generic counterpart to the generic options - ie: Task/Task<T>, TaskFactory/TaskFactory<T> - is there a reason why there is no TaskCompletionSource class?

     

     


    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".
    Friday, October 29, 2010 9:38 PM
    Moderator

Answers

  • Hi Reed-

    Thanks for the feedback.

    It's necessary for the Task<TResult> / Task case because that's the primary API for representing the operation, and the only alternative would be to have a unit/void type, which we don't have available in the Framework (at least not one that's usable).

    For TaskFactory / TaskFactory<TResult>, if we only had TaskFactory<TResult>, you'd be forced to write extra statements to return a value from every method like StartNew and ContinueWhenAll, even if you didn't care about the result, and for some methods like FromAsync it wouldn't work because the signature of the EndXx method wouldn't match.

    For TaskCompletionSource<TResult>, we could certainly have introduced a non-generic counterpart, but the additional value in doing so is minimal, namely the difference betwen writing SetResult() and SetResult(null) (or instead of null some value that represents "I don't care" for whatever stand-in type is being used.  Given the minimal difference in usability and that it doesn't actually add any capabilities, it was deprioritized for the release.  Now that with the Async CTP most Task-producing methods will be generated by the compiler, it's arguably even less of an issue.

    All that said, I certainly understand your point.  If for your own code you'd prefer a non-generic version, it's of course possible to wrap TCS<TResult> in a non-generic counterpart, e.g.

    public class TaskCompletionSource
    {
        private readonly TaskCompletionSource<object> m_tcs = new TaskCompletionSource<object>();

        public bool TrySetResult() { return m_tcs.TrySetResult(null); }
        public bool TrySetException(Exception exception) { return m_tcs.TrySetException(exception); }
        ... // etc.
    }

    Thanks, again.

    Saturday, October 30, 2010 1:19 AM
    Moderator

All replies

  • Hi Reed-

    Thanks for the feedback.

    It's necessary for the Task<TResult> / Task case because that's the primary API for representing the operation, and the only alternative would be to have a unit/void type, which we don't have available in the Framework (at least not one that's usable).

    For TaskFactory / TaskFactory<TResult>, if we only had TaskFactory<TResult>, you'd be forced to write extra statements to return a value from every method like StartNew and ContinueWhenAll, even if you didn't care about the result, and for some methods like FromAsync it wouldn't work because the signature of the EndXx method wouldn't match.

    For TaskCompletionSource<TResult>, we could certainly have introduced a non-generic counterpart, but the additional value in doing so is minimal, namely the difference betwen writing SetResult() and SetResult(null) (or instead of null some value that represents "I don't care" for whatever stand-in type is being used.  Given the minimal difference in usability and that it doesn't actually add any capabilities, it was deprioritized for the release.  Now that with the Async CTP most Task-producing methods will be generated by the compiler, it's arguably even less of an issue.

    All that said, I certainly understand your point.  If for your own code you'd prefer a non-generic version, it's of course possible to wrap TCS<TResult> in a non-generic counterpart, e.g.

    public class TaskCompletionSource
    {
        private readonly TaskCompletionSource<object> m_tcs = new TaskCompletionSource<object>();

        public bool TrySetResult() { return m_tcs.TrySetResult(null); }
        public bool TrySetException(Exception exception) { return m_tcs.TrySetException(exception); }
        ... // etc.
    }

    Thanks, again.

    Saturday, October 30, 2010 1:19 AM
    Moderator
  •  

    For TaskCompletionSource<TResult>, we could certainly have introduced a non-generic counterpart, but the additional value in doing so is minimal, namely the difference betwen writing SetResult() and SetResult(null) (or instead of null some value that represents "I don't care" for whatever stand-in type is being used.  Given the minimal difference in usability and that it doesn't actually add any capabilities, it was deprioritized for the release.  Now that with the Async CTP most Task-producing methods will be generated by the compiler, it's arguably even less of an issue.

     

    I guess I was seeing this a bit differently.  Given how the new Async CTP really standardizes on Task/Task<T> as the method for doing asynchrony, and that it's eventually going to be pervasive throughout the framework, methods for (efficiently) generating tasks by hand seem like they'll get more important, not less important.

    TaskCompletionSource is really not there for the end user's purpose as much as for the API generation portion - at least that's how its seemed to me.  It provides one of the essential building blocks for making your own Task based APIs that will work with the new Async CTP - so it seemed like it was more important for the API for generation of tasks to be clear and obvious - and it's not obvious that you'd use this to make a non-generic Task.  I think the "minimal difference in usability" statement was why I brought it up - this is one of those places where things just don't quite fit right, and it feels like a bit of a hack to to work with, especially when compared to the rest of the TPL.  

    In any case, doesn't matter for me - I'm happy using TaskCompletionSource<T> everywhere, but it did seem like an odd API choice.

     

    Thanks for the explanation - 

     

    -Reed

     


    Reed Copsey, Jr. - http://reedcopsey.com
    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".
    Saturday, October 30, 2010 3:32 AM
    Moderator