I'm trying to understand some more about async/await
and particularly how the compiler knows to "pause" at an async
method and await
without spawning additional threads.
As an example, let's say I have an async
method like
DoSomeStuff();
await sqlConnection.OpenAsync();
DoSomeOtherStuff();
I know that await sqlConnection.OpenAsync();
is where my method gets "suspended" and the thread that invoked it returns to the thread pool and once the Task
that is tracking the connection opening completes then an available thread is found to run DoSomeOtherStuff()
.
| DoSomeStuff() | "start" opening connection | ------------------------------------ |
| ---------------------------------------------------------- | DoSomeOtherStuff() - |
Here's where I get confused. I look at the source code of OpenAsync
(https://referencesource.microsoft.com/#System.Data/System/Data/Common/DBConnection.cs,e9166ee1c5d11996,references) and it's
public Task OpenAsync() {
return OpenAsync(CancellationToken.None);
}
public virtual Task OpenAsync(CancellationToken cancellationToken) {
TaskCompletionSource<object> taskCompletionSource = new TaskCompletionSource<object>();
if (cancellationToken.IsCancellationRequested) {
taskCompletionSource.SetCanceled();
}
else {
try {
Open();
taskCompletionSource.SetResult(null);
}
catch (Exception e) {
taskCompletionSource.SetException(e);
}
}
return taskCompletionSource.Task;
}
I imagine to see some place where the compiler would know to "cut off" the thread because the task has begun communicating with an external resource, but I don't really see that, and in fact, the Open();
seems to imply that it is synchronously waiting. Can someone explain how this becomes threadless "true async" code?
Your method does not necessarily gets "suspended" on await. If task you are awaiting is already completed (the case in code you provided) - method will just continue as usual. The method you are looking at is really not what is used by SqlConnection
, because DbConnection
is base class and method OpenAsync
is virtual. SqlConnection
overrides it and provides real asynchronous implementation. However, not all providers do that and those who do not will indeed use implementation you show in your question.
When such implementation is used - the whole thing will run synchronously without any thread switches. Suppose you have
public async Task Do() {
DoSomeStuff();
await sqlConnection.OpenAsync();
DoSomeOtherStuff();
}
And you use provider which does not provide real async version of OpenAsync
. Then when someone calls await Do()
- calling thread will perform all the work (DoSomeStuff
, OpenAsync
, DoSomeOtherStuff
). If that is UI thread - it will be blocked for the whole duration (such situation happens often when people use "async" methods for such providers in UI thread assuming that will somehow put the work off the UI thread, which does not happen).
The benefit of using async-await results from the fact the thread that called the following
await sqlConnection.OpenAsync();
would be released and it would be available to be utilized by from the thread pool that belongs to. If we are talking about for an ASP.NET application the thread would be freed and would be available to serve another incoming HTTP request. This way the threads of the ASP.NET thread pool would be always available to service HTTP requests and wouldn't block for instance for an I/O, like opening a connection to a database and executing some SQL statement.
Update
It should be stated here that if the task you are going to await
has been completed, your code would run synchronously.
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