Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should this code return a Task or Task<object>?

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 using Object. Then, when the Action is executed successfully, SetResult is still used to transition the Task into the RanToCompletion final state; however, since the actual result value is irrelevant, null is used. Finally, RunAsync returns Task rather than Task<Object>. Of course, the instantiated task’s type is still Task<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).

like image 344
afaolek Avatar asked Dec 19 '22 12:12

afaolek


2 Answers

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 awaits 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.

like image 107
i3arnon Avatar answered Jan 05 '23 11:01

i3arnon


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:

  1. Use an arbitrary existing type, such as object, as the return type. Set the value to null to indicate completion of the task.
  2. 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>.

like image 38
Sam Harwell Avatar answered Jan 05 '23 12:01

Sam Harwell