2013-10-31 56 views
2

案例1如何等待WinForms中的信號並同時收聽事件?

這是我的設置。

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; 
    } 
} 

我怎麼能等待兩個條件爲真

如果我做的:

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

ApiStateHandler()代碼似乎永遠不會執行。

如果我做的:

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

這工作,但似乎是對資源的浪費和黑客攻擊。

基本上我想我需要一種方式wait但沒有阻塞線程。這樣做的正確方法是什麼?

案例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對象將被創建,並且該方法需要調用ApiSend()之前等待ApiNotifyHandler執行(即,要發生的ApiFound事件)。

所以這和案例1的情況是一樣的。我應該如何等待應繼續爲真

對不起,我想盡可能提供儘可能多的信息來幫助你。

[更新]

我被迫使用的.Net 2.0。

回答

2

對付它的最好方法是重新因子代碼中使用async/await和轉ApiStateUpdate事件爲awaitable任務與TaskCompletionSourceEAP pattern)。

如果你真的要同步等待在UI線程的事件,從hereCoWaitForMultipleHandlesWaitWithDoEventshere,他們做到這一點。請記住,這種方法創建了一個嵌套的模式消息循環,可能的代碼重入是最顯着的含義(詳細討論here)。

[編輯]你在這裏要做的是一個異步到同步的橋樑,這幾乎總是一個壞主意。此外,我只是意識到你正在做一個構造函數。構造器本質上不應該有任何異步代碼,它們是原子的。總是有一個更好的方法來將一個冗長的初始化過程分解出構造函數。 @StephenCleary在他非常翔實的blog post中談到了這一點。

關於.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(從here,基於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(); 
} 
+0

我編輯了這個問題:限制到.Net 2.0。 – Yeseanul

+0

@Yeseanul,我已經更新了答案。 – Noseratio

+1

感謝您的回覆。有兩件事:'myObject.Initialized = delegate'應該使用'= ='而不是'=',並且'var'在.Net 2.0中不可用(也許你可以做這兩個更改以便我可以接受答案)。我已經測試了第一個解決方案,它工作正常。 – Yeseanul

0

基本上我覺得我需要一種方法來等待,但不阻擋 線程。

你確實想阻塞線程,而不是UI線程。因此,簡單地創建另一個線程並阻止它。使其簡單並使用BackgroundWorker()控件。

+1

[Here](http://stackoverflow.com/questions/123661/how-to-wait-for-a-backgroundworker-to-cancel)是一些有趣的資源。但我找不出使用BackgroudWorker來解決問題的方法。也許我錯過了一些東西...... – Yeseanul

+0

不......我想我誤解了這個問題。我沒有看到任何干淨的方式來做到這一點。 –

1

你將不得不異步執行阻塞代碼。否則,你會掛斷不太好的UI線程。有多種不同的方法來做到這一點。這是一個使用新的asyncawait關鍵字。我承認,事實上,這是很多東西要吞併,只有在.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. 
    } 
} 

我建議你讀asynchronous initializationStephen Cleary的博客使用asyncawait你這樣做,雖然之前。其實,通讀他所有的Async OOP系列。這真的很好。

關於情況#2

在很多方面這種情況下更容易處理,因爲構造函數和對象初始化不發揮作用。儘管如此,您仍然需要使用與上述相同的策略。首先,將API的EAP風格轉換爲TAP風格。如果這個等待條件不依賴於來自​​的事件,那麼只需在TAP方法中需要任何等待邏輯,並定期評估它。最好不要忙着等待。然後await您創建的TAP方法。不要忘記在執行此操作時將Send標記爲async

+0

感謝您的回覆。非常徹底,但我不得不使用.Net 2.0(我也編輯了這個問題來反映這一點)。 – Yeseanul