I am kinda new to C# from a heavy java background, and still trying to figure out how exactly await and async works, here's whats confusing me a lot:
Suppose I have a function likes this (Hackish code below is used for experiments only):
public static bool CheckInternetConnection()
{
Task<bool> result = CheckInternetConnectionAsync();
return result.Result;
}
public async static Task<bool> CheckInternetConnectionAsync()
{
if (NetworkInterface.GetIsNetworkAvailable())
{
SomeClass details = await ReturnARunningTask();
return details.IsSuccessful;
}
return false;
}
public Task<SomeClass> ReturnARunningTask()
{
return Task.Run(() => checkInternet());
}
in the main execution thread, if I call this:
CheckInternetConnection();
It will block indefinitely, my assumption is that control leaves CheckInternetConnectionAsync() at the "await" keyword and blocks on ".Result". When await resumes, the main thread is already blocked and stays blocked
The reason for my assumption is that I can see the task finish and return, but the code after await is never executed
however, if I do this:
bool result = Task.Run(() => CheckInternetConnection()).Result;
then the code after the await statement is executed and main thread does continue
My expectation was that it will also block, because the main thread would be blocked on the intermediate task,. the intermediate task would be blocked on the .Result
so..what made the difference? What in this case the code after await gets executed?
In a UI environment you have a special single threaded SynchronizationContext
that runs everything on the UI thread. That context is captured when you await
a task and when the task completes the method resumes on that captured context (this can be configured using ConfigureAwait
:
SomeClass details = await ReturnARunningTask().ConfigureAwait(false);
In your case when you use the Task.Result
property on a not-completed task you are synchronously blocking the UI thread and so when the CheckInternetConnectionAsync
tries to resume on the SynchronizationContext
after ReturnARunningTask
completed it can't. The UI thread is blocked waiting on the CheckInternetConnectionAsync
task which in turn waits for the UI thread, hence deadlock (blocked indefinitely).
The difference when you use Task.Run(() => CheckInternetConnection()).Result;
is that using Task.Run
offloads work to be done on a ThreadPool
thread. These threads don't have a SynchronizationContext
and so while the UI thread is blocked on .Result
the task can complete since it doesn't need the UI thread to do so.
Blocking on an asynchronous operation (sync over async
) is discouraged since it blocks threads and makes the app less responsive and scalable and it can leads to deadlocks. You should instead use async-await
all the way.
Note: Instead of doing "sync over async
" you should have the synchronous version be synchronous and if you need an async
version that offloads that CPU-intensive work to a different thread use Task.Run
:
public static bool CheckInternetConnection()
{
return checkInternet().IsSuccessful;
}
public async static Task<bool> CheckInternetConnectionAsync()
{
if (NetworkInterface.GetIsNetworkAvailable())
{
return await Task.Run(() => CheckInternetConnection());
}
return false;
}
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