Multithreading Xamarin-在异步API调用之前,我应该如何处理命令中的UI状态更改
我通常对Xamarin表单使用命令模式。最近,我所研究的UI涉及调用远程API的命令,虽然它们这样做了,但它们表明正在以多种方式进行操作。例如,ActivityIndicator应该开始旋转,当API调用返回时,页面应该禁用按钮,等等 我直观地编写了一个异步ICommand,如下所示:Multithreading Xamarin-在异步API调用之前,我应该如何处理命令中的UI状态更改,multithreading,xamarin,xamarin.forms,Multithreading,Xamarin,Xamarin.forms,我通常对Xamarin表单使用命令模式。最近,我所研究的UI涉及调用远程API的命令,虽然它们这样做了,但它们表明正在以多种方式进行操作。例如,ActivityIndicator应该开始旋转,当API调用返回时,页面应该禁用按钮,等等 我直观地编写了一个异步ICommand,如下所示: VerifyCodeCommand = new Command(async () => { await VerifyCode(_email,_code
VerifyCodeCommand = new Command(async () =>
{
await VerifyCode(_email,_code);
});
Sending = true;
(VerifyCodeCommand as Command).ChangeCanExecute();
await Task.Run(async () =>
{
try
{
// await api call here
}
catch (Exception ex)
{
App.TrackError(ex, "Login", "VerifyCode API call failed.");
}
finally
{
Sending = false;
(VerifyCodeCommand as Command).ChangeCanExecute();
}
});
}
在VerifyCode函数中,我会将绑定到ActivityIndicator
的IsRunning属性的属性更改为true,等待API调用,然后在完成时将ActivityIndicator
的IsRunning/Visible更改回false。
例如:
另一个示例是显示API失败状态(例如:红色警告),然后在API调用之前立即重置它
不幸的是,这不起作用,而且在设备.beginInvokeMainThread
上设置绑定属性也不起作用,因为(可能)ICommand持有UI线程(?)。我对这个简单的例子所做的是在命令中将属性设置为true
,然后执行Task。运行
函数体的其余部分,即API调用并重置指示器,如下所示:
VerifyCodeCommand = new Command(async () =>
{
await VerifyCode(_email,_code);
});
Sending = true;
(VerifyCodeCommand as Command).ChangeCanExecute();
await Task.Run(async () =>
{
try
{
// await api call here
}
catch (Exception ex)
{
App.TrackError(ex, "Login", "VerifyCode API call failed.");
}
finally
{
Sending = false;
(VerifyCodeCommand as Command).ChangeCanExecute();
}
});
}
这种方法是必要的还是缺少了Xamarin(表单)中的一些功能?如果总是有必要以这种方式更新UI,那么“模式化”的合理方法是什么?例如,命令是否应该始终有两个实际的lambda,一个用于初始UI状态设置,一个用于后台调用?为什么简单的单线程异步方法在这里不起作用——毕竟API调用正在等待
我想为什么问题可以归结为:为什么在主线程上调用带有wait的异步API调用看起来好像主线程没有被释放来进行UI更新(这似乎是Xamarin UI lifecycle引入了一些限制)这方面的标准解决方案是什么?我认为您的问题在于,您将等待任务过度地放在了命令的异步性质上
以当前应用程序为例:
我的所有ViewModels都有一个属性bool IsBusy
属性公共ICommand RefreshStoredDataCommand{get;}
在构造函数中:refreshstoredatacommand=new命令(async()=>wait executerfreshdatacommand(),()=>!IsBusy)代码>
对于所示的示例,我在视图中有一个ListView
控件,用于设置
RefreshCommand ="{Binding RefreshStoredDataCommand}"
IsRefreshing="{Binding IsBusy, Mode=OneWay}"
显然,对于您的代码,您应该绑定活动指示器。请标记您更新的IsRunning属性与绑定到ActivityIndicator的属性相同。您可以添加一个断点来检查这一点。对于此功能,您还可以使用来完成此工作。@y3z1问题不在于属性名称-问题在于,除非异步API调用通过Task推送到一个新线程上,否则不会发生任何事情。RunApi调用可能需要很长时间,请使用wait Task.Run
在非ui线程中执行耗时的工作,这可以使以下步骤正常工作。好的,在这一切结束后,API调用将同步运行。感谢您的努力,但使用上述代码,ActivityIndicator不会更新。实际上我已经测试过了,如果异步进程有“//Long调用”,那么这不是一个任务。运行(()=>//Long调用)生成一个新线程,那么UI不会更新。我不明白为什么。我的印象是,如果等待异步任务,主线程将被释放,这样UI就可以更新了,但是没有。该区域注释了我的位置“长时间调用异步任务是我调用远程http数据库访问任务的地方。当任务正在进行时,列表视图的循环指示器肯定会出现并循环。我会用活动指示器检查。更改IsBusy
值时是否记得触发NotifyPropertyChanged
?是的,如果//long calls部分为Task.Run,则所有操作都有效。因此,没有像NotifyPropertyChanges或错误命名的属性这样的小错误。从字面上说,从wait DoSomething()改为wait Task。运行(()=>DoSomething)就可以了。对不起,我发现了问题。我觉得我浪费了你的时间。我的实际通话毕竟是同步运行的。我应该基本上删除我的问题。我会投票给你的答案。@Frank没问题。