【问题标题】:How to update UI after async Task in viewmodel如何在视图模型中的异步任务后更新 UI
【发布时间】:2016-08-01 10:57:53
【问题描述】:

在我的Xamarin.Forms pcl 项目中,我有一个带有labelxaml 页面。我想在async task 之后更新label。在我的 ViewModel constructor 中,我为 label 设置了默认文本。并创建一个名为SomeTask()async Task 函数。

问题 1:我在哪里可以调用SomeTask() 函数。无法在constructor 中调用async Task 函数。

问题2:如何在async Task SomeTask()函数后更新Label文本。

我的代码:

  public class MyPageViewModel : ViewModelBase
    {  
        private String _selectedText;
        public String SelectedText
        {
            get { return _selectedText; }
            set {
                if (_selectedText != value)
                {
                    _selectedText = value;          
                }       
            }
        }

        public MyPageViewModel ()
        {
            _selectedText = "Welcome";   //Default text
        }

        private async Task<string> SomeTask()
        {            
            return await Task.Run(async () =>
            {
                await Task.Delay(3000); //Dummy task. It will return the status of Task.
                return "Thanks";         //Update Text       
            });         
        }
    }

【问题讨论】:

  • 你可以从构造函数中调用它——尽管你不能 await 它(那里的差别很小。)。除了返回任务,你不能做string ret = await Task...; /*update text*/ return ret;吗?
  • 您可以创建一个异步工厂方法并使您的构造函数私有。然后你调用那个方法来创建MyPageViewModel的实例在那个方法里面,你可以调用string str = await SomeTask

标签: c# async-await xamarin.forms mvvm-light


【解决方案1】:

我建议你使用my NotifyTask type;它在我的MSDN article on asynchronous MVVM data binding 中有描述,我认为这是最简单的方法:

public class MyPageViewModel : ViewModelBase
{
  private NotifyTask<string> _selectedText;
  public NotifyTask<string> SelectedText => _selectedText;

  public MyPageViewModel()
  {
    _selectedText = NotifyTask.Create(SomeTask(), "Welcome");
  }

  private async Task<string> SomeTask()
  {            
    await Task.Delay(3000);
    return "Thanks";
  }
}

然后您的数据绑定将更改为绑定到SelectedText.Result 以显示“欢迎”和“谢谢”。还有其他NotifyTask&lt;T&gt; 属性用于数据绑定,例如IsNotCompletedIsCompletedErrorMessage,它们也允许您通过数据绑定来处理故障情况。

如果你不想使用这种类型,你可以自己做类似的事情:

public class MyPageViewModel : ViewModelBase
{
  private string _selectedText;
  public string SelectedText
  {
    get { return _selectedText; }
    set
    {
      if (_selectedText != value)
      {
        _selectedText = value;
        RaisePropertyNotifyChanged(); // However you're doing this.
      }
    }
  }

  public MyPageViewModel()
  {
    _selectedText = "Welcome";
    var _ = RunSomeTask();
  }

  private async Task RunSomeTask()
  {
    try
    {
      SelectedText = await SomeTask();
    }
    catch (Exception ex)
    {
      // TODO: Handle the exception.
      // It *must* be handled here, or else it will be silently ignored!
    }
  }

  private async Task<string> SomeTask()
  {            
    await Task.Delay(3000);
    return "Thanks";
  }
}

构造函数启动RunSomeTask 操作,然后显式忽略其结果(请注意,这意味着所有异常都将被忽略)。 RunSomeTask 负责运行 SomeTask 并处理其结果(和异常)。结果仅用于更新SelectedText,异常将按照您认为适合您的应用的方式处理。

【讨论】:

    【解决方案2】:

    怎么样

    public MyPageViewModel()
    {
        _selectedText = "Welcome";   //Default text
        SomeTask().ContinueWith(previousTask => SelectedText = previousTask.Result);
    }
    

    【讨论】:

    • 不会阻塞用户界面吗?或者至少会产生意想不到的结果,因为没有等待任务,调用者可能会在任务完成之前开始使用这个实例。
    • 它不应该阻塞 UI,因为我们只是创建任务并启动它。如果我们需要在异步调用完成之前禁止调用者使用 SelectedText,我们需要额外的逻辑,但当前问题不需要。
    【解决方案3】:

    您可以创建一个异步工厂方法并将您的构造函数设为私有。然后调用该方法来创建MyPageViewModel 的实例。在该方法中,您可以调用string str = await SomeTask

    public class MyPageViewModel : ViewModelBase
    {  
         public async MyPageViewModel CreateAsync()
         {
             var model = new MyPageViewModel();
    
             SelectedText = await SomeTask();
    
             return model;
         }        
    
         private MyPageViewModel ()
         {
             _selectedText = "Welcome";   //Default text
         }
    
         private Task<string> SomeTask()
         {            
             return Task.Run(async () =>
             {
                 await Task.Delay(3000); //Dummy task. It will return the status of Task.
                 return "Thanks";         //Update Text       
             });         
          }
    }
    

    所以不要像这样创建模型:

    var model = new MyPageViewModel();
    

    你可以这样创建它:

    var model = await MyPageViewModel.CreateAsync();
    

    【讨论】:

      【解决方案4】:

      问题一:

      使用委托和事件。

      1. 创建代表和相关事件:

        • private delegate void MyDelegate();
        • private event MyDelegate myEvent;
      2. 在构造函数中订阅事件:

        • myEvent += async () =&gt; await SomeTask();
      3. 在任何需要的地方执行事件:

        • myEvent(); //Note: Check the event for null, before executing

      问题 2:

      如果在非 UI 线程上,则:

      • 使用一些框架类来执行 UI 操作:例如 - Xamarin 提供 Device.BeginInvokeOnMainThread

      • 我们可能总是将 DataBinding 与 Label 一起使用,并且仅使用事件订阅从 ViewModel 更新 Binding Path 的值。


      string _message;
      
      public string Message
      
              {
                  get => _message;
                  set
                  {
                      _message = value;
                  }
              }
      myEvent += () => Message = "New Value";
      
      <Label Text = "{Binding Message}"/>
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-04-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多