MVVM Light 如何在属性更改时调用异步方法?

4

我一直在研究,但无法找到任何确切的答案。我正在使用MVVM Light和WPF编写一个应用程序。我有一个服务被注入到我的ViewModel中来检查设置的某个属性的有效性。该服务进行Web调用并且是异步的。该调用不需要停止应用程序的执行,仅仅是为了向用户提供输入值的有效性视觉反馈。

因此,我已经把它们组合在一起使其工作了,但感觉有些不恰当。

有没有适当的方式在属性更改时执行异步方法而不使用async void之类的东西呢?

以下是我目前的代码。

    public string OmmaLicenseNumber
    {
        get => _ommaLicenseNumber;
        set
        {
            Set(() => OmmaLicenseNumber, ref _ommaLicenseNumber, value);
            Patient.OmmaLicenseNumber = value;

            var _ = CheckLicenseValid();
        }
    }

    private async Task CheckLicenseValid()
    {
        var valid = await _licenseValidationService.IsValidAsync(OmmaLicenseNumber);

        // We don't want the UI piece showing up prematurely. Need 2 properties for this;
        LicenseValid = valid;
        LicenseInvalid = !valid;
    }

如果我简单地尝试在异步方法上调用.Result,会导致死锁,并且需要重新启动应用程序才能解决。虽然我的方法可行,但我并不是很喜欢。还有其他选择吗?
3个回答

4
事件处理程序允许使用异步 void。
参考 异步/等待 - 异步编程的最佳实践
public string OmmaLicenseNumber {
    get => _ommaLicenseNumber;
    set {
        Set(() => OmmaLicenseNumber, ref _ommaLicenseNumber, value);
        Patient.OmmaLicenseNumber = value;
        //Assuming event already subscribed 
        //i.e. OmmaLicenseNumberChanged += OmmaLicenseNumberChanged;
        OmmaLicenseNumberChanged(this, 
            new LicenseNumberEventArgs { LicenseNumber = value }); //Raise event
    }
}

private event EventHandler<LicenseNumberEventArgs> OmmaLicenseNumberChanged = delegate { };
private async void OnOmmaLicenseNumberChanged(object sender, LicenseNumberEventArgs args) {
    await CheckLicenseValid(args.LicenseNumber); //<-- await async method call
}

private async Task CheckLicenseValid(string licenseNumber) {
    var valid = await _licenseValidationService.IsValidAsync(licenseNumber);

    // We don't want the UI piece showing up prematurely. Need 2 properties for this;
    LicenseValid = valid;
    LicenseInvalid = !valid;
}

//...

public class LicenseNumberEventArgs : EventArgs {
    public string LicenseNumber { get; set; }
}

我认为这种方式有点过于严格了吗?

是的,这只是一个示例,以表明它是可行的。

能否将其重构为一些更简单的辅助/实用程序方法调用?

是的。可以看起来很像一个可等待的回调模式,使用表达式获取要验证的值。


2

这里的问题不在于如何运行一个未被观察到的async任务,而在于你该如何处理异常。我之所以这样说是因为当任务被清理时,它们可能会出现。

理想情况下,只需向下一个读者展示他们将获得什么即可。由于您反对使用async void,因此需要考虑其他选项。

选项1

// running an async method unobserved 
Task.Run(async () =>
{
   try
   {
      await DoSomethingAsync();
   }
   catch (Exception e)
   {
       // Deal with unobserved exceptions 
       // or there will be dragons
   }  
});

"最初的回答":注意:这在技术上是离线处理(它将失去上下文,请注意),而且异步lambda使其成为异步void,无论如何,您都必须处理异常。
选项2更具争议:
public async void DoSomethingFireAndForget()
{
   try
   {
      await DoSomethingAsync();
   }
   catch (Exception e)
   {
      // Deal with unobserved exceptions 
      // or the will be dragons
   }  
}

选项3:两全其美:

注意:使用必要的工具来观察异常,例如Action

最初的回答:

public static class TaskUtils
{

#pragma warning disable RECS0165 // Asynchronous methods should return a Task instead of void
   public static async void FireAndForgetSafeAsync(this Task task,  Action<Exception> onErrors = null)
#pragma warning restore RECS0165 // Asynchronous methods should return a Task instead of void
   {
      try
      {
         await task;
      }
      catch (Exception ex)
      {
         onErrors?.Invoke(ex);
      }
   }
}

2
请看由Stephen Cleary编写的NotifyTask类。
这是一种处理构造函数和属性中异步操作的好方法。
你可以重构你的代码如下:
private NotifyTask _OmmaLicenseNumberChangedNotifyTask
    = NotifyTask.Create(Task.CompletedTask);
public NotifyTask OmmaLicenseNumberChangedNotifyTask
{
    get => this._OmmaLicenseNumberChangedNotifyTask;
    set
    {
        if (value != null)
        {
            this._OmmaLicenseNumberChangedNotifyTask = value;
            OnPropertyChanged("OmmaLicenseNumberChangedNotifyTask");
        }
    }
}

public string OmmaLicenseNumber
{
    get => _ommaLicenseNumber;
    set
    {
        Set(() => OmmaLicenseNumber, ref _ommaLicenseNumber, value);
        Patient.OmmaLicenseNumber = value;

        OmmaLicenseNumberChangedNotifyTask = NotifyTask.Create(CheckLicenseValid());
    }
}

然后你可以忘记这个 Task,或者你可以将你的 UI 元素绑定到 NotifyTask 的属性上,比如将 IsCompleted 绑定到某个元素上,以便只有当 Task 完成时才显示该元素。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接