Answered by:

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?

Answers

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.

All replies

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.

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.