还在等待/异步,我很难看到我在MVVM设置中如何使用它。我正在使用MvvmLight,但我想这个问题适用于任何其他MVVM框架。
假设我有一个显示宠物列表的WPF应用程序。我有一个RefreshCommand,它从WCF服务刷新列表。 UI有一个指示器,表明它正忙,并且它绑定到视图模型上的IsBusy bool属性。我的命令代码目前看起来像这样......
BackgroundWorker bw = new BackgroundWorker();
IsBusy = true;
bw.DoWork += (_, __) => {
Pets = _petService.GetPets();
IsBusy = false;
};
bw.RunWorkerAsync();
Pets是视图模型上的ObservableCollection属性。
我想使用await / async关键字,并取消BackgroundWorker。我搜索和阅读,阅读和搜索,我感到困惑。我见过的大多数文章/博客文章似乎都要求你创建自己的ICommand异步实现。为什么这有必要?鉴于MVVM和ICommand是.NET框架内置的,当然也应该内置使用async / await的能力,而无需创建我们自己的异步命令类。
我错过了什么,或者这是否真的是唯一的方法呢?
我尝试了以下内容,但它没有用......
private async void LoadAsyncData() {
IsBusy = true;
Task<ObservableCollection<Pet>> t = GetPetsAsync();
Pets = await t;
IsBusy = false;
}
private async Task<ObservableCollection<Pet>> GetPetsAsync() {
await Task.Delay(1);
return _petService.GetPets();
}
我添加了对Task.Delay(1)的调用,因为编译器警告我该方法将被称为sync。
加载了数据,但UI从未响应IsBusy属性被更改,因此用户没有看到UI正忙。
任何人都可以提供建议吗?
答案 0 :(得分:3)
你最好听一下编译器警告你的内容而不是把它放在地毯上:)之后无用的await Task.Delay
方法将继续在UI线程上,所以它基本上是同步方法 - 这就是为什么UI没有响应IsBusy
属性,它被阻止了。相反,你必须
在后台主题明确运行_petService.GetPets
:
private async void LoadAsyncData() {
IsBusy = true;
Pets = await Task.Run(() => _petService.GetPets());
IsBusy = false;
}
(更好)通过添加GetPets的异步版本来更改您的petService:
private async void LoadAsyncData() {
IsBusy = true;
Pets = await _petService.GetPetsAsync();
IsBusy = false;
}
答案 1 :(得分:2)
还在等待/异步,我很难看到我在MVVM设置中如何使用它。
我在async
MVVM上有一个由三部分组成的系列文章可能有所帮助:async
data binding,async
commands和async
services。
我想使用await / async关键字,并取消BackgroundWorker。
如果你只是想更简单地使用replacing BGW with Task.Run
,我会有一篇关于这样做的博客文章系列。
我见过的大多数文章/博文/等似乎都要求您创建自己的ICommand异步实现。为什么这有必要?
它不是必需的,但它是我(和其他人)发现有用的模式。它确保您的异步MVVM命令可以进行单元测试,并且可以从其他代码中正确调用。
鉴于MVVM和ICommand是.NET框架内置的,当然也应该内置使用async / await的能力,而无需创建我们自己的异步命令类。
.NET Framework中没有IAsyncCommand
。我相信它提供了一个有用的抽象,但并不是说抽象应该包含在每个MVVM应用程序中。
我添加了对Task.Delay(1)的调用,因为编译器警告我该方法将被称为sync。
不,那只是避免编译器警告。你真正想做的是通过使方法异步来修复警告。 Evk's answer具有适用于这两种方法的相应代码。
答案 2 :(得分:1)
BackgroundWorker
通过在后台线程上同步调用WCF服务来模拟异步。
在下面的示例中,虽然 delay 是异步的,但您将回到同步调用服务的UI线程上。
private async Task<ObservableCollection<Pet>> GetPetsAsync() {
await Task.Delay(1);
return _petService.GetPets(); //synchronous WCF call on UI thread - UI will freeze.
}
您缺少的是 true 异步WCF客户端调用 - 您必须在创建WCF代理时生成该调用。您必须选择“允许生成异步操作”和“生成基于任务的操作”。请参阅步骤6 here以供参考。
一旦你拥有了代理权限,就可以使用这样的异步版本:
private async void LoadAsyncData() {
IsBusy = true;
Pets = await _petService.GetPetsAsync(); // true async
IsBusy = false;
}
答案 3 :(得分:1)
private async void LoadAsyncData() {
IsBusy = true;
var Pets = await GetPetsAsync();
IsBusy = false;
}
private async Task<ObservableCollection<Pet>> GetPetsAsync() {
var pets = await Task<ObservableCollection<Pet>>.Run(()=>{
return _petService.GetPets();
});
return pets;
}
在GetPetsAsync方法中,启动了一个可以等待的任务,如图所示。如果设置变量(var pets),则表示正在执行闭包。完成此操作后,宠物就是可观察收集的结果,系统将不会返回,直到任务完成或出现故障。您显示的petService是同步的。
答案 4 :(得分:0)
如果您的许多命令都是异步的,请尝试ReactiveUI。您可以使用它的ICommand实现,它可以处理所有事情:在命令执行时禁用命令执行,将结果封送到UI线程等。
然后它看起来像(在SubViewModel构造函数中):
Pets = new ReactiveList<Pet>();
GetPets = ReactiveCommand.Create<IEnumerable<Pet>>(async _ => {
return await _service.GetPets();
});
GetPets.Subscribe(pets => {
using(Pets.SuppressChangeNotifications()) // it's much easier to have one list and just change the items
{
Pets.Clear();
foreach(var pet in pets)
Pets.Add(pet);
}
});
GetPets.ThrownExceptions.Subscribe(ex =>{
// show error to user, retry etc
})
_isBusy = GetPets.IsExecuting.ToProperty(this, x => x.IsBusy);
// property definition
bool IsBusy => _isBusy?.Value ?? false;