The problem is that you are using the non-generic Task
class, that is not meant to produce a result. So when you create the Task
instance passing an async delegate:
Task myTask = new Task(async () =>
...the delegate is treated as async void
. An async void
is not a Task
, it cannot be awaited, its exception cannot be handled, and it's a source of thousands of questions made by frustrated programmers here in StackOverflow and elsewhere. The solution is to use the generic Task<TResult>
class, because you want to return a result, and the result is another Task
. So you have to create a Task<Task>
:
Task<Task> myTask = new Task<Task>(async () =>
Now when you Start
the outer Task<Task>
it will be completed almost instantly because its job is just to create the inner Task
. You'll then have to await the inner Task
as well. This is how it can be done:
myTask.Start(TaskScheduler.Default);
Task myInnerTask = await myTask;
await myInnerTask;
You have two alternatives. If you don't need an explicit reference to the inner Task
then you can just await the outer Task<Task>
twice:
await await myTask;
...or you can use the built-in extension method Unwrap
that combines the outer and the inner tasks into one:
await myTask.Unwrap();
This unwrapping happens automatically when you use the much more popular Task.Run
method that creates hot tasks, so the Unwrap
is not used very often nowadays.
In case you decide that your async delegate must return a result, for example a string
, then you should declare the myTask
variable to be of type Task<Task<string>>
.
Note: I don't endorse the use of Task
constructors for creating cold tasks. As a practice is generally frowned upon, for reasons I don't really know, but probably because it is used so rarely that it has the potential of catching other unaware users/maintainers/reviewers of the code by surprise.
General advice: Be careful everytime you are supplying an async delegate as an argument to a method. This method should ideally expect a Func<Task>
argument (meaning that understands async delegates), or at least a Func<T>
argument (meaning that at least the generated Task
will not be ignored). In the unfortunate case that this method accepts an Action
, your delegate is going to be treated as async void
. This is rarely what you want, if ever.
Task.Run()
after the firstConsole.WriteLine
? – LongdistanceStart
gives you control over the task's lifetime, it doesn't. Whether you useTask.Run
orStart
, the task will have to wait until a threadpool thread is availavble to execute it. – LongdistanceI don't want to Enqueue all tasks to ThreadPool
but that's exactly what you do.Start
enqueues a task to run on a threadpool thread. – Longdistance