Asynchronous tasks

All apps need to perform tasks that may take longer than a few milliseconds. If a task blocks the UI thread for longer than a few seconds, Android will terminate it, crashing the application.

How to do it...

If we want to do work in the background, we use a new thread. To do this, we make use of the Task Parallel Library (TPL) and the async/await keywords:

  1. The first thing that is needed is the method that we wish to execute:
    public async Task DoWorkAsync() {
      await Task.Run(() => {
        // some long running task
      });
    }
  2. We then invoke it like a normal method, but just with the await keyword:
    await DoWorkAsync();
  3. We can also attach it to an event:
    doWork.Click += async (sender, args) => {
      await DoWorkAsync();
    }
  4. We can also override the void methods by simply inserting the async keyword before the return type:
    protected override async void OnCreate(Bundle bundle) {
      base.OnCreate(bundle);
      await DoWorkAsync();
    }
  5. If the method needs to return a value, we use Task<>:
    public async Task<bool> DoWorkAsync() {
      await Task.Run(() => {
        // some long running task
      });
      return true;
    }
  6. We invoke the method as usual, storing the actual value returned:
    var success = await DoWorkAsync();
  7. If we want to update the UI from a background thread, we have two methods that we can use. If we have an activity, we use the RunOnUiThread() method:
    Activity.RunOnUiThread(() => {
      // UI interaction
    });
  8. Or if we have a view, we use the Post() method:
    View.Post(() => {
      // UI interaction
    });

How it works...

One of the most important areas to consider when creating the UI of an app is how it will perform on the device. No matter how great we design the UI, if it freezes or hangs, the Android OS may terminate the app or the user will uninstall it. This is because Android is continually updating the UI and handling events. If we were to perform a long-running task, the UI would have to wait for our task to complete.

Note

If a long-running task is executed on the UI thread, the UI thread will have to wait until the task is complete before it can refresh the views. This results in the app freezing.

The most common causes of a frozen UI are network or I/O operations. If we are opening a file, then we can do so on a background thread. Although this operation is very quick, it may not be quick enough for the app or user. By moving the task into the background, the file can be opened in often less than a second but with no chance of the UI blocking.

In the case of network operations, such as downloading an image thumbnail, moving the task into the background will allow the user to scroll through a list view at high speed. As the images are downloaded, they can be displayed without causing the list to freeze until the download is complete.

To prevent the UI freezing, we use threads or do the work in the background. There are several ways to do this but the easiest and simplest is to let the compiler do all the work for us. When we do this, we make use of the TPL and the async and await keywords. We mark methods as asynchronous using the async keyword. Then, when we invoke the asynchronous method, we use the await keyword.

When we use these keywords, the compiler will rewrite our code during the build process to actually execute the method on a new thread. This is a great way to keep our code clean as well as the app performing optimally.

Tip

The compiler rewrites the code when using async and await. This allows the code to be neater and easier to maintain.

Using the async and await keywords doesn't automatically create a new thread unless we specify what to execute on a new thread. We can await the Task.Run method to execute a block of code in the background, or we can await another asynchronous method. When the awaited code is to be executed, a new thread is created and used. When the execution is finished, it is returned to the caller and back to the caller's thread.

Unlike the synchronous methods, if our method is to return a value, we have to use the generic Task<> return type. This is because, although we are returning a value, the compiler is writing additional code that requires additional members from our method. If our asynchronous method does not return a value, we use the Task return type.

So, instead of simply returning the value, such as bool, we wrap it in the generic Task<bool> tag. And, instead of void, we have a Task return type. When we use the await keyword, the compiler rewrites the code to extract the returned value, which we then use.

There is a special case in which we can await tasks within a void method. For events, this usually does not matter as the sender usually does not care what happens. For events that may require a handled notification, we must set the properties before starting the background work.

Another special case is when overriding a method that returns void. As we cannot change the return type, we have to work around it by ensuring that we invoke the base method before awaiting any tasks. If we do not, then the method will return immediately without invoking the base method, which, in the case of Android lifecycle events, is not valid.

If we want to update the UI from within a block of code that is executing on another thread, we cannot do so from that thread. All UI operations must be performed on the UI thread. Android provides two means of doing this, using an activity or the view itself.

Note

The UI cannot be updated from a background thread as the UI is not thread-safe.

We can update the UI from another thread if we have access to the activity that holds the view, using the RunOnUiThread() method defined in the activity itself. Alternatively, we can use the Post() method or the PostDelayed() method, defined on a view. These methods accept an Action instance, or a block of code, which is then executed on the UI thread.

It is important to remember that the lifetime of a background thread is not related to the lifetime of the caller. If an activity starts a new thread and then the activity is destroyed, the thread will continue to execute in the background. This may cause undesired results or crash the app. Thus, when creating a thread, it should be terminated when the caller is no longer available. To avoid running into problems, we can use threads for shorter tasks and services for longer tasks.

Tip

Background threads can be used for short tasks, but services should be used for longer tasks.

There's more...

We do have the option to use .NET threads or the thread pool along with the Android AsyncTask instance. However, this involves new types and more complex code:

private class DownloadFilesTask : AsyncTask<Uri, int, long> {
  protected override long DoInBackground(params Uri[] uris) {
    // on BACKGROUND thread
    foreach (var uri in uris) {
      if (IsCancelled)
        break;
    }
    return uris.Length;
  }
  protected override void OnProgressUpdate(params int[] progress) {
    // on UI thread
  }
  protected override void OnPostExecute(long result) {
    // on UI thread
  }
}

And then to execute this logic, we will do the following:

new DownloadFilesTask().Execute(url1, url2, url3);

See also

  • The Starting services recipe
  • The Communicating with running services recipe
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.22.100.180