2014-12-04 109 views
0

我的wpf應用程序通過通信管道連接到我的傳統應用程序。 WPF應用程序允許用戶使用界面上的按鈕繪製地圖上的位置。因此,當用戶單擊WPF應用程序用戶界面上的按鈕時,會向傳統應用程序發送管道消息,以允許用戶繪製地圖上的位置。當用戶使用鼠標在地圖上繪製位置時,使用雙向通信管道將座標發送回wpf應用程序。當我的wpf應用程序收到座標時,它需要相應地處理和執行工作流程。可能會出現一些錯誤,所以應用程序可能需要顯示錯誤消息。或者在某些情況下可能需要清除在應用程序主線程中創建的集合。所以有一個完整的代碼分支在接收到座標時被執行。在主線程中處理wpf應用程序中的呼叫

如何將我的WPF應用程序帶回到主線程,以便在收到座標時,可以執行用戶操作(如顯示消息框等)?

現在我收到異常像「收集是在不同的線程中創建的」。

我知道我可以使用此代碼顯示在主線程消息或明確集合

Application.Current.Dispatcher.Invoke((Action)(() => { PointsCollection.Clear(); })); 

Application.Current.Dispatcher.Invoke((Action)(() => { MessageBox.Show("Error"); })); 

但在單元測試中這不會工作,也是我將不得不爲此在很多地方。有沒有更好的辦法?

public void PipeClientMessageReceived(int type, string message) 
{ 
    var command = (PipeCommand)type; 
    switch (command) 
    { 
     case PipeCommand.Points: 
      { 
       string[] tokens = message.Split(':'); 
       var x = Convert.ToDouble(tokens[0]); 
       var y = Convert.ToDouble(tokens[1]); 
       SetSlotCoordinates(new Point2D(x, y)); 
      } 
      break; 
    } 
} 

SetSlotCoordinates方法實際上做所有的工作來處理座標。我試圖把這個調用放在Application.Current.Dispatcher中,但沒有成功。

Application.Current.Dispatcher.Invoke((Action)(() => { SetSlotCoordinates(new Point2D(x, y)); })); 

回答

1

不幸的是,這個問題不是很清楚。您認爲單元測試存在什麼問題會阻止您使用Dispatcher.Invoke()?當您在撥打SetSlotCoordinates()時嘗試使用Dispatcer.Invoke()時,以何種方式「沒有成功」?

基本上,使用Dispatcher.Invoke()(或它的異步兄弟,Dispatcher.BeginInvoke()應該做的工作適合你。但是,如果你能,我會建議使用新async/await模式。

沒有一個完整的代碼示例,這是不可能給你確切的代碼,但它會是這個樣子:

async Task ReceiveFromPipe(Stream pipeStream, int bufferSize) 
{ 
    byte[] buffer = new byte[bufferSize]; 
    int byteCount; 

    while ((byteCount = await pipeStream.ReadAsync(buffer, 0, buffer.Length)) > 0)  
    { 
     int type; 
     string message; 

     if (TryCompleteMessage(buffer, byteCount, out type, out message)) 
     { 
      PipeClientMessageReceived(type, message); 
     } 
    } 
} 

使用這種技術,並假設ReceiveFromPipe()方法是從UI線程調用,你就已經是在讀取時的UI線程從管道完成,使所有其他「只是工作」。

注意:我已經掩蓋了一些細節,例如在接收到完整的消息之前如何維持傳入數據的緩衝區......我假設這被封裝在假設的TryCompleteMessage()方法中。以上是爲了說明的目的,當然你必須適應你自己的特定代碼。此外,您可能會發現在後臺線程中執行更多處理更有意義,在這種情況下,您會將實際接收和處理放入單獨的async方法中;在那種情況下,該方法仍然會調用ReadAsync(),但您可以在返回值上調用ConfigureAwait(false),以便切換回UI線程直到單獨的async方法返回纔會發生。例如:

async Task ReceiveFromPipe(Stream pipeStream, int bufferSize) 
{ 
    Action action; 

    while ((action = await ReceivePoint2D(pipeStream, bufferSize)) != null) 
    { 
     action(); 
    } 
} 

async Task<Action> ReceivePoint2D(Stream pipeStream, int bufferSize) 
{ 
    byte[] buffer = new byte[bufferSize]; 
    int byteCount; 

    while ((byteCount = await pipeStream 
     .ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0)  
    { 
     int type; 
     string message; 

     if (TryCompleteMessage(buffer, byteCount, out type, out message)) 
     { 
      return PipeClientMessageReceived(type, message); 
     } 
    } 

    return null; 
} 

public Action PipeClientMessageReceived(int type, string message) 
{ 
    var command = (PipeCommand)type; 
    switch (command) 
    { 
     case PipeCommand.Points: 
      { 
       string[] tokens = message.Split(':'); 
       var x = Convert.ToDouble(tokens[0]); 
       var y = Convert.ToDouble(tokens[1]); 
       return() => SetSlotCoordinates(new Point2D(x, y)); 
      } 
      break; 
    } 
} 

在上面的例子中,異步代碼執行除了呼叫到SetSlotCoordinates()一切。爲此,它將調用包裝在Action委託中,並將其返回給UI線程,然後UI線程可以調用它。當然,您不必返回Action代表;這只是我看到的適應已有代碼的最方便的方式。您可以返回任何值或對象,並讓UI線程適當地處理它。

最後,關於上述所有內容,請注意,代碼中的任何地方都不會顯式依賴UI線程。雖然我不確定你在單元測試方面有什麼問題,但是上述應該更容易適用於沒有Dispatcher的單元測試場景,或者你不想使用它。

如果你想堅持明確使用Dispatcher,那麼你應該更具體地說明什麼是不工作的。