0

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.

JonZarate
  • 841
  • 6
  • 28
  • 1
    Context switch in async method (might) happen on first `await`. It might not happen at all, but before first `await` everything certainly runs on the same thread you called it on. – Evk May 02 '18 at 12:01
  • @Evk Any way to ensure it happens ? When I use system methods (such as `HttpClient.GetStringAsync`) they are executed in another thread (since no `NetworkOnMainThreadException` is raised). So there must be a way, right ? – JonZarate May 02 '18 at 12:08
  • 1
    Well, I cannot explain it properly in comments, and besides this was explained hundreds of times on this site. For example, change your second method to use `while ((str = await reader.ReadLineAsync()) != null)`. – Evk May 02 '18 at 12:11
  • 1
    Main thing you can extract from that is: if you don't have anything to `await` - don't use `async`. It's not designed to move your work to background thread, for that you have `Task.Run` and similar constructs. – Evk May 02 '18 at 12:15
  • @Evk I changed the line, still got the exception. This code is just a test, I actually want to use `await` and `async` to read from a DB (worker thread) and update the UI (ui thread). You said `this was explained hundreds of times`, can you guide me towards those explanations ? Does the concept have a name ? – JonZarate May 02 '18 at 12:21
  • 1
    Yes in this case it's not enough to use `reader.ReadLineAsync`, because exception is thrown by `url.OpenStream` and it does not have equivalent async version. It throws this exception because of what I described above - before first await you are certainly on UI thread (and you might be on UI thread after it too). So instead of using java classes - use .NET classes, such as `HttpWebRequest`, `HttpClient` and so on. Or use android-specific background tasks. If you want to read from DB - test by doing just that. – Evk May 02 '18 at 12:36
  • 2
    As for a guide - it's just core of how async\await works, you can read any guide about that. For example documentation: https://learn.microsoft.com/en-us/dotnet/csharp/async. There are a lot of different questions about different aspects of async\await on this site too. – Evk May 02 '18 at 12:38

3 Answers3

3

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); 
}
bommelding
  • 2,969
  • 9
  • 14
  • You said: `You await from the GUI thread and so you will block it`. I am not sure this is true, the documentation ([Asynchronous programming with async and await](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/)) says: `When you use asynchronous methods, the application continues to respond to the UI`. – JonZarate May 02 '18 at 12:06
  • `This pattern is OK for async I/O `. My `Network` method does that, yet was executed on the main thread. Could you provide more accurate examples of when a new thread is created ? My goal is to understand when `await` is executed in a new thread since I found some DB reading being done in the UI, that's why I wrote the test. My actual code reads from a DB and updates the UI with it. – JonZarate May 02 '18 at 12:34
0

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/

Juozas
  • 916
  • 10
  • 17
  • I am not looking for a solution. My question is: why aren't `LongTask` nor `Networking` executed in a worker ? – JonZarate May 02 '18 at 12:02
  • If you not interesting in extending AsyncTask class, then potential problem is here: "await LongTask();". It blocks your ui thread. Check for more info this: https://stackoverflow.com/questions/13636648/wait-for-a-void-async-method – Juozas May 02 '18 at 12:28
0

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.

JoeTomks
  • 3,243
  • 1
  • 18
  • 42