在进一步的测试中,这个类对于大多数用途来说可能是多余的。
尽管投了反对票,chawala's answer 在我的测试中运行良好。
重要的是,方法声明中async 的存在足以避免阻塞UI 线程。因此,chawala 的回答是“未破”;不值得那些反对票,恕我直言。
要明确:明确的async => await 答案当然很好,没有任何问题。如果这能让您更有信心,请使用它们。
我的回答旨在使呼叫站点更清洁。 但是,maxc 的第一条评论是正确的:我所做的与明确的async => await 不再“相同”。 到目前为止,我还没有发现任何重要的情况。无论new Command 内是否有async/await,如果您快速点击一个按钮几次,所有点击都会排队。我什至用SomeMethod 切换到新页面进行了测试。我还没有发现与明确的async/await 有任何区别。 在我的测试中,此页面上的所有答案都有相同的结果。
async void 和 async Task 一样有效,如果你没有使用 Task 结果,并且你没有添加任何代码来做一些有用的事情,但有任何例外在此方法期间发生的。
在此类代码中,请参阅我的评论“待定:考虑在此处添加异常处理逻辑。”。
或者换一种说法:大多数开发人员都在编写没有任何区别的代码。如果这是一个问题,那么在他们的new Command(await () => async SomeMethod()); 版本中同样是一个问题。
下面是一个方便类。使用它可以简化与async 的组合命令。
如果您有这样的async 方法(从接受的答案复制):
async Task SomeMethod()
{
// do stuff
}
如果没有这个类,在 Command 中使用 async 方法看起来像这样(从接受的答案复制):
resetButtonClickedCommand = new Command(async () => await SomeMethod());
有了这个类,使用就变得简单了:
resetButtonClickedCommand = new AsyncCommand(SomeMethod);
结果相当于不使用此类时显示的稍长的代码行。不是一个巨大的好处,但它很高兴拥有隐藏混乱的代码,并为一个常用的概念命名。
给定一个带参数的方法,好处变得更加明显:
async Task SomeMethod(object param)
{
// do stuff
}
无课:
yourCommand = new Command(async (param) => await SomeMethod(param));
带类(与无参数情况相同;编译器调用适当的构造函数):
yourCommand = new AsyncCommand(SomeMethod);
class AsyncCommand的定义:
using System;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows.Input;
namespace MyUtilities
{
/// <summary>
/// Simplifies using an "async" method as the implementor of a Command.
/// Given "async Task SomeMethod() { ... }", replaces "yourCommand = new Command(async () => await SomeMethod());"
/// with "yourCommand = new AsyncCommand(SomeMethod);".
/// Also works for methods that take a parameter: Given "async Task SomeMethod(object param) { ... }",
/// Usage: "yourCommand = new Command(async (param) => await SomeMethod(param));" again becomes "yourCommand = new AsyncCommand(SomeMethod);".
/// </summary>
public class AsyncCommand : ICommand
{
Func<object, Task> _execute;
Func<object, bool> _canExecute;
/// <summary>
/// Use this constructor for commands that have a command parameter.
/// </summary>
/// <param name="execute"></param>
/// <param name="canExecute"></param>
/// <param name="notificationSource"></param>
public AsyncCommand(Func<object,Task> execute, Func<object, bool> canExecute = null, INotifyPropertyChanged notificationSource = null)
{
_execute = execute;
_canExecute = canExecute ?? (_ => true);
if (notificationSource != null)
{
notificationSource.PropertyChanged += (s, e) => RaiseCanExecuteChanged();
}
}
/// <summary>
/// Use this constructor for commands that don't have a command parameter.
/// </summary>
public AsyncCommand(Func<Task> execute, Func<bool> canExecute = null, INotifyPropertyChanged notificationSource = null)
:this(_ => execute.Invoke(), _ => (canExecute ?? (() => true)).Invoke(), notificationSource)
{
}
public bool CanExecute(object param = null) => _canExecute.Invoke(param);
public Task ExecuteAsync(object param = null) => _execute.Invoke(param);
public async void Execute(object param = null)
{
// TBD: Consider adding exception-handling logic here.
// Without such logic, quoting https://docs.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming
// "With async void methods, there is no Task object, so any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started."
await ExecuteAsync(param);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}
下面是关于async void Execute 的回复。 class Command 和 interface ICommand 都有方法 void Execute。与这些兼容意味着具有相同的方法签名 - 因此通常推荐的 async Task MethodName() 不是这里的选项。请参阅我的 cmets 中的链接,了解在此处使用 void 的含义。