My original method looks like:
string DoSomeWork();
Method DoSomeWork
starts some work on another thread and returns execution ID (just random string). Later on I can query results by the returned execution ID. Main point is to make execution ID available before job will complete.
Now I want to change signature to return Task
, so user can wait if he want to.
Task DoSomeWork();
At the same time I still need to return execution ID (for tracing purposes for example) and I see a few options. First, use an out
parameter, second, return tuple with both execution ID and task (in C# this looks like not a best option), and third, about which I actually want to ask.
What if I create a class that derives Task
:
public class ExtendedTask : Task
{
public string ExecutionID {get; set;}
}
Does this look ok? Or is it better to decide other options?
P.S. In BCL there are some classes derived from Task
.
UPDATE, seems I was not able to define this clear enough. But I need access to ExecutionID before the job completes so I cannot use Task.Result
.
The recommended return type of an asynchronous method in C# is Task. You should return Task<T> if you would like to write an asynchronous method that returns a value. If you would like to write an event handler, you can return void instead. Until C# 7.0 an asynchronous method could return Task, Task<T>, or void.
You want to return a task result to the client. Set the task's Result property with your user defined result object. The task's Result property is serialized and returned back to the client.
Await & Async was built on the Task Parallel Library (TPL) which was introduced in the . NET Framework 4. Their purpose is to enable asynchronous programming.
Wait method. A call to the Wait method blocks the calling thread until the single class instance has completed execution. The following example calls the parameterless Wait() method to wait unconditionally until a task completes. The task simulates work by calling the Thread.
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.
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>
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With