3

ケース1

これが私のセットアップです。

internal class MyClass
{
    private ApiObject apiObject;

    private bool cond1;
    private bool cond2;

    internal MyClass()
    {
        this.apiObject = new ApiObject();
        this.apiObject.ApiStateUpdate += new ApiStateUpdateEventHandler(ApiStateHandler);

        //wait for both conditions to be true
    }

    private void ApiStateHandler(string who, int howMuch)
    {
        if(who.Equals("Something") && howMuch == 1)
            this.cond1 = true;
        else if(who.Equals("SomethingElse") && howMuch == 1)
            this.cond2 = true;
    }
}

両方の条件が true になるのを待つにはどうすればよいですか?

私が行った場合:

while(!(this.cond1 && this.cond2))
{
    System.Threading.Thread.Sleep(1000);
}

のコードはApiStateHandler()決して実行されないようです。

私が行った場合:

while(!(this.cond1 && this.cond2))
{
    System.Windows.Forms.Application.DoEvents();
}

これは機能しますが、リソースの浪費とハックのようです。

wait基本的に、スレッドをブロックせずに方法が必要だと思います。これを行う適切な方法は何ですか?

ケース 2

2 番目のケースはやや類似 (および関連) しており、同じ問題を示しています。

internal class MyClass
{
    private ApiNotifyClass apiNotifyClass;
    private shouldContinue = false;

    internal MyClass()
    {
        //in addition to the code from above
        this.apiNotifyClass = new ApiNotifyClass();
        this.apiNotifyClass.ApiFound += ApiNofityFoundEventHandler(ApiNotifyHandler);
    }

    internal void Send(SomethingToSend somethigToSend)
    {
        Verifyer verifier = this.apiObject.ApiGet(somethingToSend);
        this.apiNotifyClass.ApiAttach(verifier);

        //wait for the shouldContinue to be true

        this.apiObject.ApiSend(verifier);

        this.apiNotifyClass.ApiDetach(verifier);
    }

    private void ApiNotifyHandler()
    {
        this.shouldContinue = true;
    }
}

を呼び出すSend()と、Verifierオブジェクトが作成され、メソッドは がApiNotifyHandler実行される (つまり、ApiFoundイベントが発生する) のを待ってから、 を呼び出す必要がありApiSend()ます。

したがって、これはCase 1と同じ状況です。shouldContinue が true になるのをどのように待つべきですか?

非常に長い質問で申し訳ありませんが、私はあなたが私を助けるのを助けるためにできるだけ多くの情報を提供することを考えました.

アップデート

私は .Net 2.0 を使わざるを得ません。

4

3 に答える 3

2

これに対処する最善の方法は、使用するコードをリファクタリングし、イベントを( EAP パターンasync/await) を使用しApiStateUpdateて待機可能なタスクに変換することです。TaskCompletionSource

UI スレッドでイベントを同期的に待ちたい場合は、WaitWithDoEventsfrom hereまたはCoWaitForMultipleHandlesfrom hereを参照してください。このアプローチはネストされたモーダル メッセージ ループを作成することに注意してください。コードの再入可能性が最も重要な意味を持ちます (詳しくはこちらで説明します)。

[編集済み]ここでやろうとしているのは、非同期から同期へのブリッジです。これは、ほとんどの場合、それ自体では悪い考えです。さらに、コンストラクターでこれを行っていることに気付きました。コンストラクターは、その性質上、非同期コードを内部に持つべきではなく、アトミックです。コンストラクターから長い初期化手順を除外するためのより良い方法が常にあります。@StephenCleary は、非常に有益なブログ投稿でこれについて語っています。

.NET 2.0 の制限について。革新的な概念かもしれませasync/awaitんが、その背後にあるステート マシンの概念は新しいものではありません。一連のデリゲート コールバックとイベントを使用して、いつでもシミュレートできます。匿名デリゲートは .NET 2.0 から存在しています。たとえば、コードは次のようになります。

internal class MyClass
{
    private ApiObject apiObject;

    public event EventHandler Initialized;

    internal MyClass()
    {
        this.apiObject = new ApiObject();
    }

    public void Initialize()
    {
        ApiStateUpdateEventHandler handler = null;

        handler = delegate(string who, int howMuch) 
        {
            bool cond1 = false;
            bool cond2 = false;

            if(who.Equals("Something") && howMuch == 1)
                cond1 = true;
            else if(who.Equals("SomethingElse") && howMuch == 1)
                cond2 = true;           

            //wait for both conditions to be true

            if ( !cond1 && !cond2 )
                return;

            this.apiObject.ApiStateUpdate -= handler;

            // fire an event when both conditions are met
            if (this.Initialized != null)
                this.Initialized(this, new EventArgs());
        };

        this.apiObject.ApiStateUpdate += handler;
    }
}

を使用するクライアント コードは次のMyClassようになります。

MyClass myObject = new MyClass();
myObject.Initialized += delegate 
{
    MessageBox.Show("Hello!"); 
};
myObject.Initialize();

上記は、.NET 2.0 の適切な非同期イベント ベースのパターンです。より簡単ですが、より悪い解決策は、非同期から同期へのブリッジをWaitWithDoEvents(from here、 based on MsgWaitForMultipleObjects) を使用して実装することです。これは次のようになります。

internal class MyClass
{
    private ApiObject apiObject;

    internal MyClass()
    {
        this.apiObject = new ApiObject();
        Initialize();
    }

    private void Initialize()
    {
        using (ManualResetEvent syncEvent = new ManualResetEvent())
        {
            ApiStateUpdateEventHandler handler = null;

            handler = delegate(string who, int howMuch) 
            {
                bool cond1 = false;
                bool cond2 = false;

                if(who.Equals("Something") && howMuch == 1)
                cond1 = true;
                else if(who.Equals("SomethingElse") && howMuch == 1)
                    cond2 = true;           

                //wait for both conditions to be true

                if ( !cond1 && !cond2 )
                    return;

                this.apiObject.ApiStateUpdate -= handler;

                syncEvent.Set();
            };

            this.apiObject.ApiStateUpdate += handler;
            WaitWithDoEvents(syncEvent, Timeout.Infinite);
        }
    }
}

それでも、あなたの質問からのビジーな待機ループよりも効率的です。

while(!(this.cond1 && this.cond2))
{
    System.Windows.Forms.Application.DoEvents();
}
于 2013-10-31T02:44:50.783 に答える
1

ブロッキング コードを非同期で実行する必要があります。そうしないと、UI スレッドがハングアップしてしまい、良くありません。これを行うには、さまざまな方法があります。これは newasyncおよびawaitキーワードを使用するものです。これは飲み込むことが多く、.NET 4.5+でのみ実現可能であることを前もって認めます。

ケース#1について

まず、EAP (イベントベースの非同期パターン) を TAP (タスクベースの非同期パターン) に変換します。EAP は扱いにくいため、これは醜いように見えます。イベントにサブスクライブし、完了したらサブスクライブを解除する必要があります。

private Task<ApiObject> CreateApiObjectAsync()
{
  bool cond1 = false;
  bool cond2 = false;

  var tcs = new TaskCompletionSource<ApiObject>();

  ApiObject instance = null;
  ApiStateEventHandler handler = null;

  handler = (who, howmuch) =>
    {
      cond1 = cond1 || (who == "Something" && howmuch == 1);
      cond2 = cond2 || (who == "SomethingElse" && howmuch == 1);
      if (cond1 && cond2)
      {
        instance.ApiStateUpdate -= handler;
        tcs.SetResult(instance);
      }
    }

  var instance = new ApiObject();
  instance.ApiStateUpdate += handler;
  return tcs.Task;
} 

配置すると、このように使用されます。

internal class MyClass
{
    private ApiObject apiObject;

    internal MyClass()
    {
      InitializeAsync();
    }

    private async Task InitializeAsync()
    {
      apiObject = await CreateApiObjectAsync();
      // At this point the instance is created and fully initialized.
    }
}

ただし、これを行う前に、 andを使用した非同期初期化に関するStephen Clearyのブログを読むことをお勧めします。実際、彼の Async OOP シリーズをすべて読んでください。それは本当に良いです。asyncawait

ケース#2について

多くの点で、コンストラクターとオブジェクトの初期化が機能しないため、このケースは扱いが簡単です。ただし、上記と同じ戦略を使用する必要があります。まず、API の EAP スタイルを TAP スタイルに変換します。この待機条件がイベントに依存していない場合ApiObjectは、TAP メソッドで必要な待機ロジックが何であれ、定期的に評価します。これは、ビジーな待機を行わずに行うことが望ましいです。次にawait、作成した TAP メソッド。Sendただし、これを行うときは asをマークすることを忘れないでくださいasync

于 2013-10-31T03:05:34.387 に答える
0

基本的に、スレッドをブロックせずに待つ方法が必要だと思います。

UIスレッドだけでなく、スレッドをブロックしたいのです。したがって、別のスレッドを作成してブロックするだけです。簡単にして、BackgroundWorker() コントロールを使用します。

于 2013-10-31T01:49:15.777 に答える