在主线程中的wpf应用程序中处理调用

时间:2014-12-04 11:16:36

标签: c# wpf thread-safety dispatcher

我的wpf应用程序通过通信管道连接到我的遗留应用程序。 WPF应用程序允许用户使用界面上的按钮在地图上绘制位置。因此,当用户单击WPF应用程序用户界面上的按钮时,会向旧应用程序发送管道消息,以允许用户在地图上绘制位置。当用户使用鼠标在地图上绘制位置时,使用双向通信管道将坐标发送回wpf应用程序。当我的wpf应用程序收到坐标时,它需要相应地处理和执行工作流程。可能会出现一些错误,因此应用程序可能需要显示错误消息。或者在某些情况下可能需要清除在Application主线程中创建的集合。因此,在接收到坐标时会执行整个代码分支。

如何将我的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 个答案:

答案 0 :(得分:1)

不幸的是,问题不是很清楚。您认为单元测试存在哪些问题会阻止您使用Dispatcher.Invoke()?当您在Dispatcer.Invoke()的通话中尝试使用SetSlotCoordinates()时,会以什么方式出现"没有成功"?

基本上,使用Dispatcher.Invoke()(或其异步兄弟,Dispatcher.BeginInvoke()应该为您完成工作。但是,如果您能够,我建议您使用新{{1} } / async模式。

如果没有完整的代码示例,则无法为您提供确切的代码。但它看起来像这样:

await

使用这种技术,并假设从UI线程调用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); } } } 方法,当管道读取完成时,您已经在UI线程上,使其他所有内容"只是工作&# 34。

注意:我已经掩盖了一些细节,例如在收到完整邮件之前,您是如何精确维护传入数据的缓冲区的......我已假设这些信息已封装在假设的{{ 1}}方法。以上内容仅供参考,当然您必须适应自己的特定代码。

此外,您可能会发现在后台线程中执行更多处理更有意义,在这种情况下,您将实际接收和处理放入单独的ReceiveFromPipe()方法中;在这种情况下,该方法仍会调用TryCompleteMessage(),但您可以在其返回值上调用async,以便切换回UI线程直到该单独{返回{1}}方法。例如:

ReadAsync()

在上面的示例中,异步代码执行除ConfigureAwait(false)调用之外的所有操作。为此,它将调用包装在async委托中,然后将其返回到UI线程,然后UI线程可以调用它。当然,您不必返回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; } } 代表;这只是我看到的最方便的方式来调整你已经拥有的代码。您可以返回任何值或对象,并让UI线程适当地处理它。

最后,关于上述所有内容,请注意代码中没有任何地方是对UI线程的显式依赖。虽然我不确定您对单元测试有什么问题,但上述应该更容易适应单元测试场景,其中没有SetSlotCoordinates()可用或您不想使用它出于某种原因。

如果你想坚持使用Action,那么你应该更明确地确定什么不起作用。