非同期メソッドを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
_を使用することです。これについては、PrismまたはXamarin Formsをはるかに超えて多くの議論がありました。一番下の行は、ICommand
がXamarin Forms Command
とPrism DelegateCommand
の両方がICommand
のvoid Execute(object obj)
によって制限されるということです。これについてさらに情報が必要な場合は、Brian Lagunasのブログで _DelegateCommand.FromAsync
_ハンドラーが廃止された理由 を説明することをお勧めします。
一般的に、ほとんどの懸念事項は、コードを更新することで非常に簡単に処理されます。例えば。 FromAsyncが必要だった「理由」としてExceptions
についての苦情をよく耳にしますが、コードでトライキャッチがなかっただけです。 _async void
_は発火して忘れるので、私が聞いた別の不満は、コマンドが2回実行される可能性があるということです。これもDelegateCommands
ObservesProperty
とObservesCanExecute
で簡単に修正できます。
同期的に実行するメソッド(ICommand.Execute)から非同期メソッドを呼び出すときの2つの主な問題は、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);
}
}
非同期ボイド
私は個人的に「非同期の無効化」を避けています。操作がいつ終了し、エラー処理がトリッキーになるかを外部から知ることは不可能です。後者に関しては、たとえば、「非同期void」メソッドから呼び出される「非同期タスク」メソッドを記述すると、その失敗したタスクがどのように伝播されるかをほとんど認識する必要があります。
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
}
DelegateCommandを実行しているUIスレッドおよびawait式を実行しているバックグラウンドスレッドですか?
はい、UIスレッドはDelegateCommand
を実行します。 async
の場合、最初のawait
ステートメントまで実行され、その後、通常のUIスレッドの作業を再開します。 awaiterが同期コンテキストをキャプチャするように構成されている場合(つまり、not use .ConfigureAwait(false)
)、UIスレッドはDelegateCommand
の後にawait
を実行し続けます。
UIスレッドはDelegateCommandを実行していますかおよびawait式を実行しているバックグラウンドスレッド?
「待機式」がバックグラウンドスレッド、フォアグラウンドスレッド、スレッドプールスレッド、または呼び出すAPIに依存するもので実行されるかどうか。たとえば、Task.Run
を使用してCPUにバインドされた作業をスレッドプールにプッシュしたり、Stream.ReadAsync
などのメソッドを使用してスレッドをまったく使用せずに入出力操作を待機したりできます。
コンストラクターのコードを次のように変更します。
MyCommand = new DelegateCommand(() => { MyJobASync()});
そしてあなたの方法で:
private async Task MyJobASync()
{
// your method
}