所以我一直在做很多挖掘,而且我无法在任何地方找到任何真正明确的答案。
我正在使用MVVM Light和WPF编写应用程序。我有一个注入我的ViewModel的服务,它检查设置的某个属性的有效性。该服务进行Web调用,它是异步的。该调用不需要停止执行该应用程序,而只是为了向用户提供关于输入值的有效性的视觉反馈。
因此,我设法将一些东西放在一起以使其工作,但感觉有点hackey。
在不诉诸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
,则会导致死锁,需要重新启动应用程序才能修复。虽然我的工作,我不是一个粉丝。我还有什么其他选择?
事件处理程序允许使用async void
参考Async/Await - Best Practices in Asynchronous Programming
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; }
}
我认为这有点重手吗?
是。这仅仅是为了表明它是可行的。
这可以重构为一些更简单的帮助/实用程序方法调用吗?
是。可能看起来很像一个等待回调模式使用表达式获取值来验证
这里的问题不是如何运行未观察到的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
}
});
注意:这在技术上是卸载的(它将失去上下文BEWARE)并且async
lamda无论如何都会使它成为异步空白,但是在任何情况下你都必须处理异常
选项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);
}
}
}
也许Stephen Cleary将会在此发生,并给你一个更有说服力的解释
看看Stephen Cleary写的NotifyTask
-Class。
这是在构造函数和属性中处理异步操作的好方法。
你可以重构你的代码:
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
完成时才能看到一些东西。