我想将异步方法链接到 Xamarin.Forms 中 prism 框架中的委托命令,我的问题是如何做到这一点?
以下解决方案正确吗?是否存在任何陷阱? (死锁、UI 缓慢或冻结、不良做法......)
{ // My view model constructor
...
MyCommand = new DelegateCommand(async () => await MyJobAsync());
...
}
private async Task MyJobAsync()
{
... // Some await calls
... // Some UI element changed such as binded Observable collections
}
您可以直接使用
async void
。然而,根据我的经验,有一些笔记...
代码的结构是:启动异步操作,然后用结果更新 UI。对我来说,这意味着您会更好地使用
NotifyTask<T>
异步 数据绑定 方法,而不是 命令。请参阅我的 async MVVM 数据绑定文章 ,了解有关 NotifyTask<T>
背后设计的更多信息(但请注意,最新代码 有错误修复和其他增强功能)。
如果您确实需要异步命令(这种情况很少见),您可以直接使用async void
或构建异步命令类型,如我在关于异步MVVM命令的文章中所述。我也有类型来支持这一点,但这些 API 正在不断变化。 如果您选择直接使用
async void
:
async Task
逻辑公开,或者至少可供您的单元测试访问。
DelegateTask
一样,您的代表的任何异常都必须得到正确处理。
如果您使用 Prism 库:https://docs.prismlibrary.com/docs/commands/commanding.html#implementing-a-task-based-delegatecommand
如果您想将CommandParameter
传递给
DelegateCommand
,请在
DelegateCommand
变量声明中使用此语法
public DelegateCommand<object> MyCommand { get; set; }
在 ViewModel 的构造函数中以这种方式初始化它:
MyCommand = new DelegateCommand<object>(HandleTap);
其中 HandleTap
被声明为
private async void HandleTap(object param)
希望有帮助。
async void
。对此已有很多讨论,而不仅仅是 Prism 或 Xamarin Forms。底线是
ICommand
Xamarin Forms
Command
和 Prism
DelegateCommand
都受到
ICommand
的
void Execute(object obj)
的限制。如果您想获得更多相关信息,我鼓励您阅读 Brian Lagunas 的博客,解释为什么
DelegateCommand.FromAsync
处理程序已过时。 通常,通过更新代码可以很容易地解决大多数问题。例如。我经常听到抱怨
Exceptions
是 FromAsync 必要的“原因”,结果却在他们的代码中看到他们从未有过 try catch。因为
async void
是即发即忘,我听到的另一个抱怨是一个命令可能执行两次。这也可以通过
DelegateCommands
ObservesProperty
和
ObservesCanExecute
轻松解决。
public sealed class AsyncDelegateCommand : ICommand
{
private readonly Func<object, Task> func;
private readonly Action<Exception> faultHandlerAction;
private int callRunning = 0;
// Pass in the async delegate (which takes an object parameter and returns a Task)
// and a delegate which handles exceptions
public AsyncDelegateCommand(Func<object, Task> func, Action<Exception> faultHandlerAction)
{
this.func = func;
this.faultHandlerAction = faultHandlerAction;
}
public bool CanExecute(object parameter)
{
return callRunning == 0;
}
public void Execute(object parameter)
{
// Replace value of callRunning with 1 if 0, otherwise return - (if already 1).
// This ensures that there is only one running call at a time.
if (Interlocked.CompareExchange(ref callRunning, 1, 0) == 1)
{
return;
}
OnCanExecuteChanged();
func(parameter).ContinueWith((task, _) => ExecuteFinished(task), null, TaskContinuationOptions.ExecuteSynchronously);
}
private void ExecuteFinished(Task task)
{
// Replace value of callRunning with 0
Interlocked.Exchange(ref callRunning, 0);
// Call error handling if task has faulted
if (task.IsFaulted)
{
faultHandlerAction(task.Exception);
}
OnCanExecuteChanged();
}
public event EventHandler CanExecuteChanged;
private void OnCanExecuteChanged()
{
// Raising this event tells for example a button to display itself as "grayed out" while async operation is still running
var handler = CanExecuteChanged;
if (handler != null) handler(this, EventArgs.Empty);
}
}
异步无效
我个人会不惜一切代价避免“async void”。从外部无法知道操作何时完成,并且错误处理变得很棘手。对于后者,例如编写从“async void”方法调用的“async Task”方法几乎需要知道其失败的任务是如何传播的:
public async Task SomeLogic()
{
var success = await SomeFurtherLogic();
if (!success)
{
throw new DomainException(..); // Normal thing to do
}
}
然后有人在另一天写道:
public async void CommandHandler()
{
await SomeLogic(); // Calling a method. Normal thing to do but can lead to an unobserved Task exception
}
是的,UI 线程运行UI 线程是否运行 DelegateCommand 且后台线程是否运行等待表达式?
DelegateCommand
。如果是
async
,它会运行直到第一个
await
语句,然后恢复其常规 UI 线程工作。如果等待者配置为捕获同步上下文(即,您不使用
.ConfigureAwait(false)
),UI线程将在
DelegateCommand
之后继续运行
await
。
UI 线程是否运行 DelegateCommand“await 表达式”是否在后台线程、前台线程、线程池线程或其他线程上运行取决于您调用的 api。例如,您可以使用并且后台线程运行等待表达式?
Task.Run
将 cpu 密集型工作推送到线程池,或者您可以使用
Stream.ReadAsync
等方法等待 i/o 操作而不使用任何线程
public ICommand MyCommand{get;set;}
//constructor
public ctor()
{
MyCommand = new Xamarin.Forms.Command(CmdDoTheJob);
}
public async void DoTheJob()
{
await TheMethod();
}
public DelegateCommand MyCommand => new DelegateCommand(MyMethod);
private async void MyMethod()
{
}
没有陷阱。异步方法中的 void 返回类型是专门为委托创建的。如果您想更改已反映在 UI 上的某些内容,请在此块中插入相关代码:
Device.BeginOnMainThread(()=>
{
your code;
});
实际上,ICommand 和 DelegateCommand 非常相似,所以上面的答案是非常正确的。