OnClick_Event发生时立即更新UI

时间:2017-08-03 08:20:55

标签: c# multithreading xamarin xamarin.forms ui-thread

很简单的例子:

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

代码背后:

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();
  }
} 

我使用MVVM - INotifyPropertyChanged

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

ObservableObject实现可以在这里看到:https://codeshare.io/G87N74

当我在几秒钟(4或5)后点击按钮时,按钮文字获得价值&#39;结束03/08/2017 08:55:33&#39; (它当然取决于当前的时间戳)。按钮文本Start + DateTime不会出现。

如果我这样写,它会起作用:

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();
}

但它不是100%,因为我们不知道线程将如何安排执行。当我们点击事件方法时,是否有一种简单的方法立即更新UI

有一个方法Device.BeginInvokeOnMainThread(),但它返回void,因此它不会阻止UI。

1 个答案:

答案 0 :(得分:5)

我们无法立即从后台线程对UI进行更改。来自后台线程的UI线程上的所有操作将在下一个UI线程循环中执行。如果您的应用程序没有被某些密集型任务阻止,那么它将尽可能接近立即

如果你想让你的第一个例子运作良好,那就把这些繁重的操作放到backgorund任务中:

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();

此处还有来自Xamarin的关于线程的文档:

  

应用程序用户界面始终是单线程的,即使在多线程设备中 - 只有一种屏幕表示,所显示的内容的任何更改都需要通过单个“访问点”进行协调。这可以防止多个线程同时尝试更新同一个像素(例如)!