web-dev-qa-db-ja.com

DelegateCommandでasyncメソッドを使用する方法

非同期メソッドを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
}
14
sorosh_sabz

既に述べたように、デリゲートコマンドで非同期コードを処理する方法は_async void_を使用することです。これについては、PrismまたはXamarin Formsをはるかに超えて多くの議論がありました。一番下の行は、ICommandがXamarin Forms CommandとPrism DelegateCommandの両方がICommandvoid Execute(object obj)によって制限されるということです。これについてさらに情報が必要な場合は、Brian Lagunasのブログで _DelegateCommand.FromAsync_ハンドラーが廃止された理由 を説明することをお勧めします。

一般的に、ほとんどの懸念事項は、コードを更新することで非常に簡単に処理されます。例えば。 FromAsyncが必要だった「理由」としてExceptionsについての苦情をよく耳にしますが、コードでトライキャッチがなかっただけです。 _async void_は発火して忘れるので、私が聞いた別の不満は、コマンドが2回実行される可能性があるということです。これもDelegateCommandsObservesPropertyObservesCanExecuteで簡単に修正できます。

3
Dan S.

同期的に実行するメソッド(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
}
1
Thomas Zeman

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などのメソッドを使用してスレッドをまったく使用せずに入出力操作を待機したりできます。

1
Haukinger

コンストラクターのコードを次のように変更します。

 MyCommand = new DelegateCommand(() => { MyJobASync()});  

そしてあなたの方法で:

private async Task MyJobASync()
{
   // your method
}
0
Igor Monteiro