How the synchronizationContext makes your live easier

Last month I went to Xamarin Evolve. A lot of great new stuff was announced like workbooks, the iOS simulator for windows, the Xamarin.Forms previewer,… But besides all those big announcements there were a lot of small takeaways that make your life as a developer easier. One of those small tips is the SynchronizationContext.

Suppose you have a button. If you click on the button, you want to call a service to view your external IP address. You wire up the click events in each platform, but the rest of the logic goes in your PCL.

Let’s have a look at the Android code. First wire up the button in the OnCreate, add an event handler that calls your PCL:

protected override void OnCreate(Bundle savedInstanceState)
{
  // ...
  // wire up the click event in your activity
  Button button = FindViewById<Button>(Resource.Id.myButton);
  button.Click += SearchAsync;
  // ...
}

// The event handler that calls the PCL -> _controller.SearchIp
private async void SearchAsync(object sender, EventArgs e)
{
  var endpoint = await _controller.SearchIpAsync(AnErrorOccured);
  if (!string.IsNullOrEmpty(endpoint.ip))
  {
    _txtResult.Text = $"Your IP is: {endpoint.ip}";
  }
}

The searchIp method is an async method. When the runtime is done executing the SearchIpAsync method it will return to the original context. In our case the UIThread. That is the behavior we want because when the method is done we try to update the later on we update the TextView _txtResult and as we know only the UIThread can update UI elements.

We also pass an Action delegate into the SearchIpAsync method. When an error occurs, we went the Action to execute (in our case AnErrorOccured must be executed). Typically that Action contains a RunOnUIThread method:

private void AnErrorOccured(string message)
{
  // Only the original thread that created a view hierarchy can touch its view
  // If you don't use the SynchronizationContext in the controller and forget the RunOnUiThread you get an AndroidRuntimeException 
  RunOnUiThread(() =>
  {
    _txtResult.Text = message;
  });
}

So, why do we need the RunOnUIThread? Well, we don’t want our controller to execute a lot of code on the UIThread. We want to free the UIThread as much as possible for, well, UI stuff. So, when executing async methods we use the ConfigureAwait(false) on each async method. But here comes the problem. When an error occurs, we want the AnErrorOccured method to execute and that method must run on the UIThread because it updated a UI element. The RunOnUiThread is easy to forget and you need to implement it in your Android and iOS code. If you want to tell in your PCL that the Action delegate needs to run on the UIThread, you can use the SynchronizationContext.

Using the SynchronizationContext is quite simple. At the beginning of your method in your PCL you capture the current context (which is often the UIThread). Once your Action delegate needs to execute, you run in on the captured context:

public async Task<Endpoint> SearchIpAsync(Action<string> onError)
{
  //...
  // capture the current context
  var context = SynchronizationContext.Current;
  try
  {
    // Do some stuff
    // an exception occurs
    throw new Exception("I blew up");
  }
  catch (Exception ex)
  {
    // Instead of executing the Action delegate directly
    // onError(ex.Message);
    // we capture the original context and execute the Action delegate on that context
    context.Post(unused => 
      {  
        onError(ex.Message);
      },null);
  }
  //...
}

That’s it, now you can get rid of the RunOnUIThread methods in your Activity or iOS Controller. So the eventhandler becomes:

private void AnErrorOccured(string message)
{
  // Statement below won't cause problems if you've used the SynchronizationContext in the controller
  _txtResult.Text = message;
}

You can find the complete example on my GitHub.