2014-12-04 90 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,那么你应该更具体地说明什么是不工作的。

相关问题