Expected result:
TestAsync
is called by UI thread and a worker thread executes LongTask
.
Actual result:
Ui thread executes everything
Test:
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// [...]
_fab = root.FindViewById<FloatingActionButton>(...);
_fab.Click += ((sender, v) => TestAsync("fab"));
// [...]
}
private async void TestAsync(string origin)
{
await LongTask();
}
private async Task LongTask()
{
while (true) { } // Thread should hung here
}
Outcome: The Ui freezes.
Test 2:
In order to make sure the UI is executing everything, I made a network operation instead (which is not allowed in the UI thread in Android)
public async Task<int> Network(string s)
{
URL url = new URL("http://www.randomtext.me/api/");
Java.IO.BufferedReader reader = new Java.IO.BufferedReader(new Java.IO.InputStreamReader(url.OpenStream()));
int count = 0;
string str;
while ((str = reader.ReadLine()) != null) {
count += str.Length;
}
reader.Close();
await Task.Delay(3000); // To make sure this method is compiled as async even though it isn't necessary
return count;
}
Outcome: NetworkOnMainThreadException
.
Question:
Why aren't LongTask
nor Network
methods executed in a worker thread ? What are await
/async
for then ?
Thanks.
and a worker thread executes LongTask.
No, that won't happen by itself. You await from the GUI thread and so you will block it. This pattern is OK for async I/O because that will free up the Thread.
But when your case is CPU bound, there is no use for async/await, use Task.Run:
private void TestAsync(string origin)
{
Task.Run( LongTask);
}
Main rule is:
- ui related behavior must be in main thread (ui thread)
- networking/heavy tasks must be performed in non main (ui) thread.
You try to perform networking task from main (ui) thread, it caused you got this exception.
Try to create TestAsync class extending AsyncTask class.
Then override doInBackground, onPostExecute methods.
- Make your networking logic in doInBackground method.
- Then update your UI in onPostExecute method.
Reference on this: http://programmerguru.com/android-tutorial/what-is-asynctask-in-android/
Task one: async/await
private async void TestAsync(string origin)
{
await LongTask();
}
Explanation:
When the buttons click event delegate is invoked, it is invoked from the main dispatcher (UI Thread). It tells the delegate to call TestAsync("Fab")
Synchronously. When the delegate runs through the test async method, it is told to run the Task LongTask
but you are also telling it to await the outcome of that Task by using the 'await' request. So the TestAsync
method cannot complete until the LongTask
has been completed; as this is requested from the main dispatcher the rest of the UI hangs until it's completed.
Resolution:
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// [...]
_fab = root.FindViewById<FloatingActionButton>(...);
_fab.Click += ((sender, v) => TestAsync("fab"));
// [...]
}
private async void TestAsync(string origin)
{
// By not calling await you tell the code that you don't need this
// calling method to await a return value.
// using Task.Run explicitly requests another thread process this request
Task.Run(LongTask);
}
private async Task LongTask()
{
while (true) { } // Thread should hung here
}
Task two: Network
public async Task<int> Network(string s)
{
URL url = new URL("http://www.randomtext.me/api/");
Java.IO.BufferedReader reader = new Java.IO.BufferedReader(new Java.IO.InputStreamReader(url.OpenStream()));
int count = 0;
string str;
while ((str = reader.ReadLine()) != null) {
count += str.Length;
}
reader.Close();
await Task.Delay(3000); // To make sure this method is compiled as async even though it isn't necessary
return count;
}
Explanation:
As before this largely depends on how the task is being started, see this quote from the Microsoft documentation:
The async and await keywords don't cause additional threads to be
created. Async methods don't require multithreading because an async
method doesn't run on its own thread. The method runs on the current
synchronization context and uses time on the thread only when the
method is active. You can use Task.Run to move CPU-bound work to a
background thread, but a background thread doesn't help with a process
that's just waiting for results to become available.
I hope this answer adds a little more detail to be used alongside the other responses to your question.