Is it ok to derive from TPL Task to return more details from method?

I would recommend using Task<T> instead, as it allows you to "embed" the other information in the Task's Result.

For example, in your case, it might make sense to have something like:

class ExecutionResult
{
     public int ExecutionID { get; set; }
     public string Result { get; set; }
     // ...
}


public Task<ExecutionResult> DoSomeWork()
{
     return Task.Factory.StartNew( () =>
     {
          // Replace with real work, etc...
          return new ExecutionResult { ExecutionID = 0, Result = "Foo" };
     });
}

Edit in response to comments:

If you need the data "before" the Task completes, and are trying to access this for other purposes, I would recommend making a class that contains the Task and the other data, and returning it, ie:

class ExecutionResult
{
     public int ExecutionID { get; private set; }
     public Task<string> Result { get; private set; }
     // ... Add constructor, etc...
}


public ExecutionResult DoSomeWork()
{
     var task = Task.Factory.StartNew( () =>
     {
          // Replace with real work, etc...
          return "Foo";
     });

     return new ExecutionResult(1, task); // Make the result from the int + Task<string>
}

This will still let you access the information about your process, and the Task/Task<T>.


I wouldn't personally extend Task<T>, I'd compose it instead. That way you don't need to worry about any APIs which only return Task<T> - you can just wrap the task. You can have a property which exposes the underlying task, and for the C# 5 async purposes you can implement the awaiter pattern on your own type - but it feels to me like creating your own derived type is likely to do more harm than good. It's mostly a gut feeling though.

Another option is to work the other way round: store your extra state in the Task.AsyncState property; that's what it's there for, after all. That way you can easily pass the task around without losing the execution context it's logically part of.


If you do decide to inherit from Task or Task<TResult>, you might encounter the frustration that the Action<Object> or Func<Object,TResult> delegate that provides the actual work for the task must be specified at the time your Task-derived object is constructed, and cannot be changed later. This is true even though the base class constructor(s) do not Start() the newly created task, and in fact it may not be started until much later, if ever at all.

This makes it difficult to use a Task-derived class in situations where instances must be created prior to the full details of its eventual work being available.

An example might be a amorphous network of well-known Task<TResult> nodes working on a shared goal such that they access each other's Result properties in an ad-hoc manner. The simplest way to guarantee that you can Wait() on any arbitrary node in the network is to pre-construct all of them prior to starting any of them. This neatly avoids the problem of trying analyze work graph dependencies, and allows runtime factors to determine when, if, and in what order Result values are demanded.

The problem here is that, for some of the nodes, you may not be able to provide the function that defines the work at construction time. If creating the necessary lambda function requires closing over Result values from other tasks in the network, the Task<TResult> which provides the Result we want might not have been constructed yet. And even if it happens to have been constructed earlier during the pre-construction phase, you can't call Start() on it yet since it might incorporate dependencies on other nodes which have not. Remember, the whole point of pre-constructing the network was to avoid complexities like these.

As if this weren't enough, there are other reasons it's inconvenient to have to use a lambda function to provide the desired function. Because it's passed into the constructor as an argument, the function can't access the this pointer of the eventual task instance, which makes for ugly code, especially considering the lambda is necessarily defined under the scope of--and possibly inadvertent closure over--some unrelated this pointer.

I could go on, but the bottom line is that you shouldn't have to endure runtime closure bloat and other hassles when defining extended functionality in a derived class. Doesn't that miss the whole point of polymorphism? It would be more elegant to define the work delegate of a Task-derived class in the normal way, namely, an abstract function in the base class.

Here's how to do it. The trick is to define a private constructor which closes over one of its own arguments. The argument, passed as null by (chained) callees, acts as a placeholder variable which you can close over to create the delegate required by the Task base class. Once you're in the constructor body, the 'this' pointer is available, so you can substitute the actual function pointer into the closed-over argument, replacing null. Note that it won't be "too late" to do this because it's impossible for the outer delegate to have been invoked yet.

For deriving from 'Task':

public abstract class DeferredActionTask : Task
{
    private DeferredActionTask(Action _a, CancellationToken ct, TaskCreationOptions opts)
        : base(_ => _a(), null, ct, opts)
    {
        _a = this.action;
    }

    protected DeferredActionTask(
            CancellationToken ct = default(CancellationToken),
            TaskCreationOptions opts = TaskCreationOptions.None)
        : this(default(Action), ct, opts)
    {
    }

    protected abstract void action();
};

For deriving from 'Task<TResult>':

public abstract class DeferredFunctionTask<TResult> : Task<TResult>
{
    private DeferredFunctionTask(Func<TResult> _f, CancellationToken ct, TaskCreationOptions opts)
        : base(_ => _f(), null, ct, opts)
    {
        _f = this.function;
    }

    protected DeferredFunctionTask(
            CancellationToken ct = default(CancellationToken),
            TaskCreationOptions opts = TaskCreationOptions.None)
        : this(default(Func<TResult>), ct, opts)
    {
    }

    protected abstract TResult function();
};

Remember, as with any other uses of constructed Task instances, the Task will not automatically be started upon construction, so with this technique you still have to explicitly call Start() at some point later on. Of course, as discussed above, here that is the whole point.

Finally, notice that I made the private constructors always pass null for the state argument of the base Task constructor, and that this essentially prevents ever setting the AsyncState read-only property to a useful value. You can change this to include passing-through such a value if you like, but again the reason here is that the whole point is to eliminate the requirement that startup data be pre-determined. It hardly makes sense—when you now have your own entire derived class to populate, at any time prior to calling Start, with relevant instance data—to have to single out, at a logically unrelated time, likely wildly in advance, exactly one "special" data parameter to represent the details of the task's eventual, useful work.