8

非同期呼び出しを同期に変換するための良い習慣(パターン)はありますか?
私は、メソッドがすべて非同期であるサードパーティのライブラリを持っています。almoustの結果を取得するには、イベントをリッスンする必要のあるメソッドを取得します。これにより、イベントにコンテキストがもたらされます。基本的には次のようになります。

service.BeginSomething(...);
service.OnBeginSomethingCompleted += ;

私が必要としているのは、BeginSomethingが実際に完了した後(つまり、OnBeginSomethingCompletedがトリガーされた後)にコードを実行することです。イベントでの応答を処理することは非常に不便です。

私が考えることができる唯一の方法は、Thread.Sleepループを実行し、フォームの一部のフィールドが更新されるまで待つことですが、それは非常にエレガントなソリューションのようには見えません。

.net4.0を使用しています。

4

7 に答える 7

8

メインクラスをサブクラス化し、操作の同期バージョンを提供できます。サブクラス化がオプションでない場合は、拡張メソッドを作成できます。これがどのように見えるかです。

public class Subclass : BaseClass
{
  public void Something()
  {
    using (var complete = new ManualResetEventSlim(false))
    {
      EventHandler handler = (sender, args) => { complete.Set(); };
      base.OnBeginSomethingCompleted += handler;
      try
      {
        base.BeginSomething();
        complete.Wait();
      }
      finally
      {
        base.OnBeginSomethingCompleted -= handler;
      }
    }
  }
}

アップデート:

私が指摘すべきことの1つは、これが場合によっては問題になる可能性があるということです。この例を考えてみましょう。

var x = new Subclass();
x.BeginSomething();
x.Something();

handlerinは、への前回の呼び出しからイベントをSomething受信できることは明らかです。どういうわけかこれを防ぐようにしてください。OnBeginSomethingCompletedBeginSomething

于 2012-04-12T13:40:34.577 に答える
2

他の人が言ったように、可能であれば、あなたはあなた自身のコードを非同期にすることを試みるべきです。それが機能しない場合、サードパーティのライブラリは標準BeginXXXEndXXX非同期パターンをサポートしていますか?もしそうなら、TPLを使用すると物事が簡単になります。コードは次のようになります。

using System.Threading.Tasks;

...

var task = Task<TResult>.Factory.FromAsync(
    service.BeginSomething, service.EndSomething, arg1, arg2, ..., null);

task.Wait();
var result = task.Result;

使用する特定のオーバーロードは、渡す必要のあるパラメーターの数によって異なります。あなたはここでリストを見ることができます。

于 2012-04-12T13:30:33.207 に答える
2

を使用しManualResetEventます。service.BeginSomething()同期ラッパーで作成し、状態オブジェクトの一部として呼び出しに渡します。呼び出しの直後にWaitOne()、これはブロックされます。

service.OnBeginSomethingCompleted状態オブジェクトから抽出して設定すると、同期呼び出し元のブロックが解除されます。

于 2012-04-12T13:31:35.350 に答える
1

ReactiveExtensionsを見たいと思うかもしれません

Rxを使用すると、基本的にそれを「イベント」にラップできますsomeClass.SomeEvent.Subscribe(d=>...)。通常、必要なものを処理するためにラムダ式を使用してサブスクライブするようなことを行います。またObserveOn、GUIスレッドでそれを処理するために使用します(詳細を参照してください。これは単なるヒントです)。

他のオプションは使用することasync awaitです(これはVS 2010で使用できるようになりました)。

お役に立てれば

注:Rxは非同期メソッドをネイティブでサポートしており、ほとんど1回の呼び出しでそれらをRxイベントに変換します。FromAsyncPatternを見てくださいObservable.FromAsyncPattern

于 2012-04-12T13:32:17.637 に答える
1

(代理人が行うように)をBeginSomething()返す場合は、そこからを取得できます。IAsyncResult.BeginInvokeWaitHandle

service.OnBeginSomethingCompleted += ;
var asyncResult = service.BeginSomething();
asyncResult.AsyncWaitHandle.WaitOne(); // Blocks until process is complete

ちなみに、非同期プロセスの開始後にイベントハンドラーを割り当てることにより、イベントが登録される前に非同期呼び出しが完了し、イベントが発生しないという競合状態が発生します。

于 2012-04-12T13:34:15.897 に答える
0

最新のソフトウェア開発(Windowsプラットフォームでも)の一般的な傾向は、非同期で可能なことを実行することです。

実際、Windows8ソフトウェアの設計ガイドラインによると、コードが50ミリ秒を超えて実行される場合は、非同期である必要があります。

したがって、スレッドをブロックすることはお勧めしませんが、代わりにそのライブラリの恩恵を受けて、「待って、来てください」などの見栄えの良いアニメーション、またはプログレスバーをユーザーに提供します。

つまり、スレッドをブロックせず、アプリで何が起こっているかをユーザーに通知し、非同期のままにします。

于 2012-04-12T13:29:43.580 に答える
0

この解決策はBrianGideonの解決策に似ていますが、あなたがやろうとしていることについては少しきれいだと思います。Monitorオブジェクトを使用して、Completedイベントがトリガーされるまで呼び出し元のスレッドを待機させます。

public class SomeClass : BaseClass
{
   public void ExecuteSomethingAndWaitTillDone()
    {
        // Set up the handler to signal when we're done
        service.OnBeginSomethingCompleted += OnCompleted;

        // Invoke the asynchronous method.
        service.BeginSomething(...);

            // Now wait until the event occurs
            lock (_synchRoot)
            {
                // This waits until Monitor.Pulse is called
                Monitor.Wait(_synchRoot);
            }
    }

    // This handler is called when BeginSomething completes
    private void OnCompleted(object source, ...)
    {
        // Signal to the original thread that it can continue
        lock (_synchRoot)
        {
            // This lets execution continue on the original thread
            Monitor.Pulse(_synchRoot);
        }
    }

    private readonly Object _synchRoot = new Object();
}
于 2013-09-21T18:43:33.213 に答える