8

メソッドを非同期的に呼び出すことに関するCodeProjectの次のコードスニペットを... http://www.codeproject.com/Articles/14931/Asynchronous-Method-Invocationで見つけました。

private void CallFooWithOutAndRefParameters()
{
    // create the paramets to pass to the function
    string strParam1 = "Param1";
    int intValue = 100;
    ArrayList list = new ArrayList();
    list.Add("Item1");

    // create the delegate
    DelegateWithOutAndRefParameters delFoo =
      new DelegateWithOutAndRefParameters(FooWithOutAndRefParameters);

    // call the beginInvoke function!
    IAsyncResult tag =
        delFoo.BeginInvoke(strParam1,
            out intValue,
            ref list,
            null, null);

    // normally control is returned right away,
    // so you can do other work here...

    // calling end invoke notice that intValue and list are passed
    // as arguments because they might be updated within the function.
    string strResult =
        delFoo.EndInvoke(out intValue, ref list, tag);

    // write down the parameters:
    Trace.WriteLine("param1: " + strParam1);
    Trace.WriteLine("param2: " + intValue);
    Trace.WriteLine("ArrayList count: " + list.Count);
    Trace.WriteLine("return value: " + strResult);
}

このコードについて私が理解していないことがいくつかあります。

コメントごとに、コントロールはBeginInvoke行に到達するとすぐに呼び出し元のコードに返されます。

これは、次のコード(EndInvokeの後にトレースログが続く)が、FooWithOutAndRefParameters呼び出しが完了した後にのみ実行されることを意味します...自動的に(そのコードが同じメソッドに存在する場合でも)。それは私には少し混乱しているように見えます。(私はいつもこの種のことのためにコールバックを使用しました。)

このメソッドを使用して、EndInvokeを呼び出す必要がありますか。メソッドを非同期で呼び出して、それが発生したことを忘れることはできますか?これの欠点はありますか?

EndInvokeを呼び出さない場合(このメソッドに示されているように)、常にコールバックが必要ですか?コールバックが何もしない場合でも。

答えがあなたがすべきであるなら...それならあなたはEndInvokeを呼び出すか、それともコールバックを定義しますか?(コールバックを定義することの利点は、結果が通知されることです)

ところで、EndInvokeまたはコールバックでエラーをチェックしたり結果をログに記録したりできることはわかっています(実際にはそうするかもしれません)。私が疑問に思っていたのは、EndInvokeを呼び出さなかったり、コールバックを定義したりしないことによるリスクがありますか(たとえば、メモリリーク)?ベストプラクティスは何ですか。

セス

4

3 に答える 3

15

はい、EndInvoke() を呼び出す必要があります。そうしないと、10 分間続くかなり厄介なリソース リークが発生します。基礎となる配管は .NET Remoting です。10 分は、えーと、「デフォルトのライフタイム リース時間」です。これを頻繁に行うと、プログラムがキーオーバーします。デリゲート ターゲットに 10 分以上かかると、興味深い結果も得られます。

しかし、最も重要なことは、呼び出された操作の結果ではない場合、呼び出されたメソッドが正常に完了したかどうかを確認する必要があることです。例外で終了した場合は、EndInvoke() を呼び出すまで、それについてはわかりません。その時点で、例外が再発生します。実際にその例外を処理するのはかなりトリッキーです。なぜなら、デリゲート ターゲットが爆発する前に、プログラムの状態がどれだけ変更されたかがわからないからです。あなたが本当にそれを捕まえたいのであれば、ターゲットがあまりにも多くの副作用を持っていないことが非常に重要です. または逆に、ターゲットが例外をキャッチして再スローし、必要に応じて状態の復元を実行します。

あなたが使用した例はもちろんかなりばかげたものであり、 1 つのメソッドで BeginInvoke 呼び出しと EndInvoke 呼び出しの間で何か役に立つことを行うのは難しいです。BeginInvoke 呼び出しの最後から 2 番目の引数である、登録できるコールバックに常に依存します。つまり、nullサンプル コードのようにパスしないでください。また、BackgroundWorker や Task などのクラス、さらには ThreadPool.QueueUserWorkItem() を優先して、このコードの煩わしさを取り除きます。デリゲートの BeginInvoke() メソッドの使用は、非常に低レベルのハッキングです。

デリゲートは .NET の非常に強力な抽象化ですが、その機能をうまく分散していませんでした。それらを使用してイベントを実装することは定型的であり、問​​題はありません。その BeginInvoke() メソッドを適切に使用することは、黒帯の芸術です。注目すべき詳細は、.NETCore では機能しなくなったことです。リモート処理のサポートが CoreCLR から削除されました。

于 2012-07-23T21:22:14.070 に答える
5

何よりもまず、これらのリンクが興味深いかもしれません:

同期メソッドの非同期呼び出し

Delegate.EndInvoke() は本当に必要ですか?


さて、あなたの質問について:

コメント コントロールは、BeginInvoke 行に到達するとすぐに呼び出し元のコードに返されます。

はい、呼び出しは非同期で行われます (別のスレッドを使用していると思われるかもしれません)。

それは、次のコード (EndInvoke の後にいくつかのトレース ログが続く) は、FooWithOutAndRefParameters 呼び出しが完了した後にのみ実行されることを意味しますか...自動的に (そのコードは同じメソッドに存在しますが)。私には少し混乱しているように見えます。(私は常にこの種のコールバックを使用してきました。)

EndInvokeBeginInvoke によって開始されたスレッド (メソッド) が終了するまで実行をブロックします。このコンテキストでは、スレッドの結合に似ています。

このメソッドを使用すると、EndInvoke を呼び出す必要があります。メソッドを非同期で呼び出して、それが起こったことを忘れることはできますか? これには何か欠点がありますか?

必ず電話する必要がありますEndInvoke(下記参照)。それにはおそらく多くの理由がありますが、最も重要だと思うのは、メソッドが失敗した場合、例外をスローすることにより、EndInvoke が呼び出されるまで例外を取得できないことです。

EndInvoke を呼び出さない場合 (このメソッドで示されているように)、常にコールバックを使用する必要がありますか? コールバックが何もしない場合でも。

EndInvokeその場合、コールバックはコールバックから呼び出す必要があります。したがって、コールバックのみがオプションです。

答えがあなたがすべきである場合... EndInvokeを呼び出しますか、それともコールバックを定義しますか? (コールバックを定義する利点は、結果が通知されることです)

コールバックを定義する必要はありませんが、定義する場合はその中で EndInvoke を呼び出します。

メソッドが終了したことを完全に非同期で通知されるか、メソッドとの結合を強制する (呼び出しスレッドをブロックする) か、どちらのシナリオが優れているかを理解するのはあなた次第です。それはすべて制御に関するものであり、どちらか、または両方を行う必要があります。

ところで、EndInvokeまたはコールバックでエラーをチェックしたり、結果をログに記録したりできることはわかっています(実際にそうするかもしれません)。私が疑問に思っていたのは、EndInvoke を呼び出さないこと、またはコールバックを定義しないこと (メモリ リークなど) によるリスクはありますか? ベストプラクティスは何ですか。

本質的にではありません、いいえ、リスクがあるとは思いません。ただし、メソッドが失敗したか、正常に完了したかを常に確認する必要があります。


MSDNから、BeginInvoke の後に実行できるオプションは次のとおりです。

  • いくつかの作業を行ってから、EndInvoke を呼び出して、呼び出しが完了するまでブロックします。

  • IAsyncResult.AsyncWaitHandle プロパティを使用して WaitHandle を取得し、その WaitOne メソッドを使用して、WaitHandle が通知されるまで実行をブロックしてから、EndInvoke を呼び出します。

  • BeginInvoke によって返された IAsyncResult をポーリングして、非同期呼び出しがいつ完了したかを判断し、EndInvoke を呼び出します。

  • コールバック メソッドのデリゲートを BeginInvoke に渡します。メソッドは、非同期呼び出しが完了すると、ThreadPool スレッドで実行されます。コールバック メソッドは EndInvoke を呼び出します。


OBS: @ScottChamberlain がコメントで述べたように、MSDN は次のように述べています。

必要に応じて、デリゲートから戻り値を取得するために呼び出すことができますEndInvokeが、これは必須ではありません。EndInvoke は、戻り値を取得できるまでブロックします。

その背後にある理由は、コントロールを扱うときはUIスレッドで操作していると思います。EndInvoke はスレッドをブロックするため、そうしたくない理由があるかもしれません。それでも、メソッドが正常に完了したことを確認するために、コールバックまたは完了のポーリングを使用することをお勧めします。これにより、プログラムがより堅牢になります(またはエラー耐性が高くなります)。

于 2012-07-23T21:03:22.567 に答える
1

実際には、EndInvoke を呼び出す必要があります。これは、呼び出し元が処理には関係ないイベントにサブスクライブされたハンドラーを持っている可能性があるためですが、既知の時間に特定の種類の処理を確実に実行できるように、消費者にとっては重要な場合があります。アプリケーション状態。

于 2012-07-23T20:54:04.737 に答える