我想将异步方法链接到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
}
答案 0 :(得分:10)
您可以直接使用async void
。但是,从我的经验中得到了一些注释......
代码的结构是:启动异步操作,然后使用结果更新UI。这意味着,对于异步数据绑定而不是命令采用NotifyTask<T>
方法,您会得到更好的服务。有关NotifyTask<T>
背后的设计的更多信息,请参阅我的async MVVM data binding article(但请注意latest code有错误修正和其他增强功能)。
如果你真的做需要异步命令(这种情况要少得多),你可以直接使用async void
或者像我一样构建异步命令类型我在async MVVM commmands的文章中描述。我也有types to support this,但这些API更不稳定。
如果您确实选择直接使用async void
:
async Task
逻辑设为公开,或者至少可以进行单元测试。DelegateTask
一样,必须正确处理代理中的任何异常。答案 1 :(得分:2)
正如已经提到的,使用delegate命令处理异步代码的方法是使用async void
。关于这一点已经有很多讨论,远远超出了Prism或Xamarin Forms。底线是ICommand
Xamarin表单Command
和Prism DelegateCommand
受ICommand
void Execute(object obj)
的限制。如果您想获得更多相关信息,我建议您阅读Brian Lagunas的博客,解释为什么DelegateCommand.FromAsync
handler is obsolete。
通常,通过更新代码可以非常轻松地处理大多数问题。例如。我经常听到关于Exceptions
的抱怨和#34;原因&#34;为什么FromAsync是必要的,只是在他们的代码中看到他们从来没有尝试过捕获。因为async void
是火和忘记,我听到的另一个抱怨是命令可以执行两次。使用DelegateCommands
ObservesProperty
和ObservesCanExecute
也可轻松解决此问题。
答案 2 :(得分:1)
UI线程是否正在运行DelegateCommand 和后台线程运行await表达式?
是的,UI线程运行DelegateCommand
。如果是async
,它将运行到第一个await
语句,然后恢复其常规UI线程工作。如果awaiter配置为捕获同步上下文(即,不使用.ConfigureAwait(false)
),则UI线程将继续在DelegateCommand
之后运行await
}。
UI线程是否正在运行DelegateCommand 和后台线程运行await表达式?
&#34;是否等待表达&#34;运行在后台线程,前台线程,线程池线程或任何取决于您调用的API。例如,您可以使用Task.Run
将cpu绑定的工作推送到线程池,或者您可以等待i / o操作而不使用任何线程,例如Stream.ReadAsync
答案 3 :(得分:1)
我认为从同步执行的方法(ICommand.Execute)调用异步方法时的两个主要问题是1)拒绝在前一个调用仍在运行时再次执行2)处理异常。两者都可以通过以下(原型)实现来解决。这将是DelegateCommand的异步替换。
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
我个人会避免&#34; async void&#34;尽一切代价。当操作完成并且错误处理变得棘手时,不可能从外部知道。关于后者,例如编写&#34;异步任务&#34;从&#34; async void&#34;调用的方法方法几乎需要知道它的失败任务是如何传播的:
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
}
答案 4 :(得分:1)
只需查看以下链接,如果您使用的是棱镜库:https://prismlibrary.com/docs/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)
希望有帮助。
答案 5 :(得分:0)
将构造函数中的代码更改为:
MyCommand = new DelegateCommand(() => { MyJobASync()});
并在您的方法中:
private async Task MyJobASync()
{
// your method
}
答案 6 :(得分:-1)
public ICommand MyCommand{get;set;}
//constructor
public ctor()
{
MyCommand = new Xamarin.Forms.Command(CmdDoTheJob);
}
public async void DoTheJob()
{
await TheMethod();
}
答案 7 :(得分:-1)
public DelegateCommand MyCommand => new DelegateCommand(MyMethod);
private async void MyMethod()
{
}
没有陷阱。异步方法中的void返回类型是为代理创建的。如果您想要更改在UI上反映的内容,请在此块中插入相关代码:
Device.BeginOnMainThread(()=>
{
your code;
});
实际上,ICommand和DelegateCommand非常相似,所以上面的答案是完全正确的。