Should this code return a Task or Task<object>?
-
21-12-2019 - |
Question
I was reading The Nature of TaskCompletionSource, a post by Stephen Toub.
public static Task RunAsync(Action action)
{
var tcs = new TaskCompletionSource<Object>();
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
action();
tcs.SetResult(null);
}
catch(Exception exc) { tcs.SetException(exc); }
});
return tcs.Task;
}
Since we no longer care what the type of
T
is, I’ve defaulted to usingObject
. Then, when theAction
is executed successfully,SetResult
is still used to transition theTask
into theRanToCompletion
final state; however, since the actual result value is irrelevant,null
is used. Finally,RunAsync
returnsTask
rather thanTask<Object>
. Of course, the instantiatedtask
’s type is stillTask<Object>
, but we need not refer to it as such, and the consumer of this method need not care about those implementation details.
I don't particularly understand why the method should return Task
rather than Task<object>
(which is why I emphasised the bold sentence). I know the method is set to return Task
but tcs
is a TaskCompletionSource<Object>
, not TaskCompletionSource
(which is wrong, I think).
Solution
There isn't a non generic TaskCompletionSource
and considering all you want is a task without a result, the result doesn't matter.
The caller doesn't know and doesn't care in this case that the Task is actually a Task<object>
, The caller just await
s it, and gets an exception if there is one. The caller is unaware of the actual result.
This of course is facilitated by the fact that Task<T>
inherits from Task
It's also common to find a Task<bool>
that returns false, or Task<int>
with 0.
OTHER TIPS
There is no non-generic TaskCompletionSource
class for creating instances of Task
which are not instances of Task<T>
. This leaves two options for the generic type parameter for TaskCompletionSource<T>
when you don't care about (or are not providing) the return value:
- Use an arbitrary existing type, such as
object
, as the return type. Set the value tonull
to indicate completion of the task. - Use a specific non-public type, and set the value to
null
to indicate completion of the task.
When I create a TaskCompletionSource<T>
instance for the purpose of providing a Task
with no return value, I prefer to use a dedicated non-public type to ensure consuming code will not mistake the returned Task
as an instance of Task<T>
where the result has meaning.
First, I define the following class (it can be a private sealed class
if it's nested within another type):
internal sealed class VoidResult
{
}
Then, instead of using TaskCompletionSource<object>
for the completion source, I use TaskCompletionSource<VoidResult>
. Since the VoidResult
type is not accessible by calling code, the user will be unable to cast the Task
object to an instance of Task<VoidResult>
.
I don't particularly understand why the method should return
Task
rather thanTask<object>
Because when you return Task<Object>
it means that when this method completes it will produce some useful value of type Object
. in this case we're not producing any result, That's why stephen choose to return Task
.
If we're dealing with Func<Object>
then returning Task<Object>
would be appropriate, as Func
will produce some result, we may choose to return it.
Why
TaskCompletionSource<Object>
, notTaskCompletionSource
?
Because there's no such thing. There is no non generic TaskCompletionSource
.
If you returned a Task<object>
, then var result = await RunAsync(...)
would always return null
, since that's what you're setting the result to.
The client doesn't care about this, so you just return a Task
.
Ideally, you would use a TaskCompletionSource
internally, instead of a TaskCompletionSource<object>
, and just call something like SetCompleted()
instead of SetResult(null)
. But such type does not exist.