12

If I have a method like

Task<bool> LongProcessTaskAsync();

Would it be a better practice to return a started task

return Task<bool>.Factory.StartNew(() => { ... });

or just return new Task<bool>(() => ...)

Personally, I prefer the first method but I'd rather be consistent will other API's and libraries.

Is returning a not-started task ever more appropriate?

karl.r
  • 961
  • 1
  • 11
  • 28

2 Answers2

18

In the case of async/await methods, the Task will already be started. AFAIK, all the BCL methods added for Task-based versions return already-started Tasks. It would be kinda weird not to, since the common consumer case is now:

var foo = await GetFooAsync();

[EDIT] Based on Stephen pointing out that the TAP guidelines covers this (and he already includes a link to the guidelines), I'll include a quote of the relevant bit from page 4 (in The Task-based Asynchronous Pattern Defined -> Behavior -> Task Status), and I've added bold+italics around the key parts.

Task Status

The Task class provides a life cycle for asynchronous operations, and that cycle is represented by the TaskStatus enumeration. In order to support corner cases of types deriving from Task and Task as well as the separation of construction from scheduling, the Task class exposes a Start method. Tasks created by its public constructors are referred to as “cold” tasks, in that they begin their life cycle in the non-scheduled TaskStatus.Created state, and it’s not until Start is called on these instances that they progress to being scheduled. All other tasks begin their life cycle in a “hot” state, meaning that the asynchronous operations they represent have already been initiated and their TaskStatus is an enumeration value other than Created.

All tasks returned from TAP methods must be “hot.” If a TAP method internally uses a Task’s constructor to instantiate the task to be returned, the TAP method must call Start on the Task object prior to returning it. Consumers of a TAP method may safely assume that the returned task is “hot,” and should not attempt to call Start on any Task returned from a TAP method. Calling Start on a “hot” task will result in an InvalidOperationException (this check is handled automatically by the Task class).

zastrowm
  • 8,017
  • 3
  • 43
  • 63
James Manning
  • 13,429
  • 2
  • 40
  • 64
  • 2
    +1. The [Task-Based Asynchronous Pattern guidelines](http://www.microsoft.com/en-us/download/details.aspx?id=19957) state the returned `Task` should be started. Any `async` code using your method would expect it to be started, particularly if it follows the TAP naming guidelines (i.e., ending in `Async`). – Stephen Cleary Jul 29 '12 at 13:23
  • 2
    Non-started tasks are really only used by Task Parallel Library code. There are other `Task` APIs inherited from TPL that shouldn't be used in TAP code: `Task.Wait`, `Task.WaitAll`, `Task.WaitAny`, `Task.Result` - essentially anything dealing with blocking. – Stephen Cleary Jul 29 '12 at 13:33
  • 1
    @StephenCleary suppose I call `var myTask = DoWorkAsync();` (without await) in the first line of some async code, then I call some others as well like `var myOtherTask = DoMoreWorkAsync();`. Then I do some other stuff, now before ending my method I need to be sure these two tasks (which started asynchronously) had finished. I'd either use `Task.Wait(myTask, myOtherTask);` or `await myTask; await myOtherTask;`. You wanted to mean my first option shouldn't be used, and that I should instead use `await` (with `ConfigureAwait(false)`), or did I misunderstood? – Alisson Reinaldo Silva Jan 31 '17 at 14:09
  • 2
    @Alisson: If it's at the end of the method, I'd use `await Task.WhenAll(myTask, myOtherTask).ConfigureAwait(false)`. – Stephen Cleary Jan 31 '17 at 14:14
3

James Manning correctly answered. Here is another angle: Why would anyone want an unstarted task? If he did, he could have just waited calling the method. He could have called it later, or wrapped it in a Lazy or future himself. There is almost never a reason not to return a started task.

usr
  • 168,620
  • 35
  • 240
  • 369
  • 3
    An unstarted `Task` can be executed in any context. So - in theory - you could use an unstarted `Task` as a unit of work where the execution context doesn't matter (i.e., the `TaskScheduler` is left up to the caller). However, I've never seen anyone do this in practice. – Stephen Cleary Jul 29 '12 at 20:51
  • Ok, never thought of that. Probably it is best to pass in a TaskScheduler to the method called so it can decide if it actually *wants* to be executed on a different TaskScheduler. – usr Jul 29 '12 at 20:53