3

Very simple example:

<StackLayout>
  <Button Text="{Binding LoginButtonText}" Clicked="Button_Clicked"></Button>
</StackLayout>

Code behind:

public partial class ItemsPage : ContentPage
{
  private ViewModels.ItemsViewModel _viewModel;
  public ItemsPage()
  {
    _viewModel = new ItemsViewModel();
    BindingContext = _viewModel;
    InitializeComponent();
  }
  private void Button_Clicked(object sender, EventArgs e)
  {         
    this._viewModel.LoginButtonText = "Start" + DateTime.Now.ToString();
    // this loop is just for testing purposes. To demonstrate 
    // that this loop block UI thread
    for (int i = 0; i < 100; i++)
    {
      for (int j = 0; j < 1000; j++)
      {
        string s = new Random(45).NextDouble().ToString();
      }
    }
    this._viewModel.LoginButtonText = "End " + DateTime.Now.ToString();
  }
} 

I'm using MVVM - INotifyPropertyChanged

public class ItemsViewModel : ObservableObject
{ 
  private string _loginButtonText;
  public string LoginButtonText
  {
    get { return _loginButtonText; }
    set { SetProperty(ref _loginButtonText, value); }
  }
}

ObservableObject implementation can be seen here: https://codeshare.io/G87N74

When I click on the button after a few seconds (4 or 5) the button text gets value 'End 03/08/2017 08:55:33' (will it depends on the current timestamp of course). The button text Start + DateTime does not appear.

It works if I write this:

private void Button_Clicked(object sender, EventArgs e)
{
  this._viewModel.LoginButtonText= "Start" + DateTime.Now.ToString();
  // I assume here execution switches threads as it sees Task as a new thread. While waiting for task to finish
  // it finished mvvm INotifyPropertyChanged change.Invoke call and updates the button text
  await Task.Delay(5000);
  this._viewModel.LoginButtonText = "End " + DateTime.Now.ToString();
}

But it's not 100% as we don't know how threads will be scheduled for execution. Is there a simple way to update UI immediately when we hit event method?

There is a method Device.BeginInvokeOnMainThread() but it returns void, therefore it doesn't block UI.

broadband
  • 3,266
  • 6
  • 43
  • 73

1 Answers1

5

We cannot make changes to UI immediately from background thread. All operations on UI thread from background thread will execute on next UI thread cycle. And if your application is not blocked by some intensive tasks, it will be as close to immediately as we can reach.

If you want to make your first example to work well, put that heavy operations to backgorund task:

this._viewModel.LoginButtonText = "Start" + DateTime.Now.ToString();

//With await Task.Run (creating new thread) on heavy operation this thread is not blocked
//so previous operation is scheduled to UI thread and changes will appear on screen
await Task.Run(() =>
{
    for (int i = 0; i < 100; i++)
    {
        for (int j = 0; j < 1000; j++)
        {
            string s = new Random(45).NextDouble().ToString();
        }
    }
});
this._viewModel.LoginButtonText = "End " + DateTime.Now.ToString();

Also here is documentation from Xamarin about threads:

Application user interfaces are always single-threaded, even in multi-threaded devices – there’s only one representation of the screen and any changes to what is displayed need to be coordinated through a single ‘access point’. This prevents multiple threads from trying to update the same pixel at the same time (for example)!

Paweł Kanarek
  • 2,317
  • 2
  • 10
  • 17
  • 1
    Thank you for documentation about [Working with the UI Thread](https://developer.xamarin.com/guides/ios/user_interface/ios-ui/ui-thread) – broadband Aug 09 '17 at 13:46
  • 1
    Although the quote that you provided is specially for **iOS**. I assume it works the same in Android. – broadband Aug 09 '17 at 14:08