I've done this Unit Test and I don't understand why the "await Task.Delay()" doesn't wait !
[TestMethod]
public async Task SimpleTest()
{
bool isOK = false;
Task myTask = new Task(async () =>
{
Console.WriteLine("Task.BeforeDelay");
await Task.Delay(1000);
Console.WriteLine("Task.AfterDelay");
isOK = true;
Console.WriteLine("Task.Ended");
});
Console.WriteLine("Main.BeforeStart");
myTask.Start();
Console.WriteLine("Main.AfterStart");
await myTask;
Console.WriteLine("Main.AfterAwait");
Assert.IsTrue(isOK, "OK");
}
Here is the Unit Test output :
How is this possible an "await" doesn't wait, and the main thread continues ?
new Task(async () =>
A task does not take a Func<Task>
, but an Action
. It will call your asynchronous method and expect it to end when it returns. But it does not. It returns a task. That task is not awaited by the new task. For the new task, the job is done once the method returned.
You need to use the task that already exists instead of wrapping it in a new task:
[TestMethod]
public async Task SimpleTest()
{
bool isOK = false;
Func<Task> asyncMethod = async () =>
{
Console.WriteLine("Task.BeforeDelay");
await Task.Delay(1000);
Console.WriteLine("Task.AfterDelay");
isOK = true;
Console.WriteLine("Task.Ended");
};
Console.WriteLine("Main.BeforeStart");
Task myTask = asyncMethod();
Console.WriteLine("Main.AfterStart");
await myTask;
Console.WriteLine("Main.AfterAwait");
Assert.IsTrue(isOK, "OK");
}
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.
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