i have a simple controller with two methods Index and IndexAsync synchronous and asynchronous respectively. I try to do same as written in this article . But something going wrong , instead of 5 seconds result i get 12. How come , what's the reason ?
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
return View();
}
[HttpGet]
public async Task<string> IndexAsync()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
await Delay(3);
await Delay(5);
await Delay(4);
stopwatch.Stop();
return stopwatch.Elapsed.Seconds.ToString();
}
private async Task<Int32> Delay(int sec)
{
await Task.Delay(1000 * sec);
return sec;
}
}
the result:
Many people have a bad model in their heads of what await
does. Despite it's name, they somehow come to believe that it starts things happening. Nothing could be further from the truth 1 .
await
has some expression on its right-hand side. It doesn't care what that expression is, how it goes about its work, etc. All it cares about is that the expression is going to produce something that is awaitable 2 . By far the most common awaitables you'll encounter are Task
and Task<T>
.
And then await
does its simple job - is this awaitable thing finished ? If so, we'll get the result (if any) and then carry on. If not , then we can't make any further progress. If someone else can make good use of our thread in the meantime, we'll let that happen. And when that awaitable thing does finish, we've arranged to resume execution of the method containing the await
.
I will just reiterate - await
doesn't care how or why that awaitable got created. That's someone else's job - here, it's being done by the async
machinery that transformed your Delay
method. But await
doesn't care that that method is marked async
(that's an implementation detail of that method, not part of its signature, despite where it appears), just that it promises to return a "hot" Task
- one that's already running.
1 I think it's mostly because some people have learned the approximate equality async ≅ parallelism ≅ using threads
. It was never really true but this is what we're constantly fighting to correct. And so they think " async
must mean we're creating threads"
2 Indeed, in the article you linked to, and the issue already addressed by Rawling
the expressions there referred to variables that had previously been initialized by calls to Task
-returning methods. Which is what made your own code different.
In the article, the code starts all three tasks and then awaits all three tasks :
var contentTask = service.GetContentAsync(); // start task 1
var countTask = service.GetCountAsync();
var nameTask = service.GetNameAsync();
var content = await contentTask; // wait until task 1 is finished
var count = await countTask;
var name = await nameTask;
Your code starts and awaits each task in turn :
await Delay(3); // start task 1 and wait until it is finished
await Delay(5);
await Delay(4);
Make the following code modification, for the Async calls to be executed together not one after another :
var result = await Task.WhenAll(Delay(3),Delay(4),Delay(5));
Task.WhenAll
will provide the representative Task
, which will finish, when all the tasks in the collection supplied are finished execution (Success / Failure). So we just await
the representative task
and get the necessary behavior, though it still may not guarantee 5 sec as expected. result
will be an int[]
, which will contain value from each call to Delay method.
In your case, the Delay
method takes the responsibility of beginning the Task, using await Task.Delay(1000 * sec);
, therefore if you just call the Delay
as shown by the accepted answer, then separately await
, then they still get executed in parallel, but let's assume, you are not sure or the Delay method is as follows:
private Task Delay(int sec)
{
return Task.Delay(1000 * sec);
}
then using await Task.WhenAll(...)
becomes important, since it will start the tasks which are not started, otherwise will simply wait for them to finish. Otherwise using the accepted answer you will not receive the benefit.
More regarding await
as general information, from the C# 6.0 specification guide:
The task of an await expression is required to be awaitable. An expression t is awaitable if one of the following holds:
t has an accessible instance or extension method called GetAwaiter
with no parameters and no type parameters, and a return type A for which all of the following hold:
System.Runtime.CompilerServices.INotifyCompletion
(hereafter known as INotifyCompletion
for brevity) The purpose of the GetAwaiter
method is to obtain an awaiter for the task. The type A is called the awaiter type for the await expression.
The purpose of the IsCompleted
property is to determine if the task is already complete. If so, there is no need to suspend evaluation.
The purpose of the INotifyCompletion.OnCompleted
method is to sign up a "continuation" to the task; ie a delegate (of type System.Action
) that will be invoked once the task is complete.
The purpose of the GetResult method is to obtain the outcome of the task once it is complete.
This outcome may be successful completion, possibly with a result value, or it may be an exception which is thrown by the GetResult method.
The expression await t
is classified the same way as the expression (t).GetAwaiter().GetResult()
. Thus, if the return type of GetResult is void
, the await_expression
is classified as nothing. If it has a non-void return type T
, the await_expression
is classified as a value of type T
.
At runtime, the expression await t is evaluated as follows:
(t).GetAwaiter()
If b is false then evaluation depends on whether a implements the interface System.Runtime.CompilerServices.ICriticalNotifyCompletion
(hereafter known as ICriticalNotifyCompletion
for brevity). This check is done at binding time; ie at runtime if a has the compile time type dynamic , and at compile time otherwise. Let r denote the resumption delegate (Iterators):
ICriticalNotifyCompletion
, then the expression (a as (INotifyCompletion)).OnCompleted(r)
is evaluated. (a as (ICriticalNotifyCompletion)).UnsafeOnCompleted(r)
is evaluated. Either immediately after (if b was true )
, or upon later invocation of the resumption delegate (if b was false )
, the expression (a).GetResult()
is evaluated. If it returns a value, that value is the result of the await_expression. Otherwise the result is nothing.
An awaiter's implementation of the interface methods INotifyCompletion.OnCompleted
and ICriticalNotifyCompletion.UnsafeOnCompleted
should cause the delegate r to be invoked at most once. Otherwise, the behavior of the enclosing async function is undefined.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.