繁体   English   中英

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

[英]Process a call in wpf Application in Main thread

我的wpf应用程序通过通信管道连接到我的旧应用程序。 WPF应用程序允许用户使用界面上的按钮在地图上绘制位置。 因此,当用户单击WPF应用程序用户界面上的按钮时,管道消息将发送到旧版应用程序,以允许用户在地图上绘制位置。 当用户使用鼠标在地图上绘制位置时,将使用2向通信管道将坐标发送回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));  }));

不幸的是,这个问题不是很清楚。 您认为单元测试会导致哪些问题阻止您使用Dispatcher.Invoke() 当您尝试在对Dispatcer.Invoke()的调用上使用SetSlotCoordinates() ,以什么方式“没有成功”?

基本上,使用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);
        }
    }
}

使用这种技术,并假设从UI线程调用了ReceiveFromPipe()方法,那么当完成从管道的读取之后,您将已经在UI线程上,从而使其他所有事情都“正常工作”。

注意:我已经详细介绍了一些细节,例如在接收到完整的消息之前,您如何精确地维护传入数据的缓冲区...我假设它封装在假设的TryCompleteMessage()方法中。 上面是出于说明目的,当然您必须适应自己的特定代码。

另外,您可能会发现在后台线程中执行更多处理是更有意义的,在这种情况下,您会将实际的接收和该处理放入单独的async方法中。 在那种情况下,该方法仍将调用ReadAsync() ,但是您可以在该方法的返回值上调用ConfigureAwait(false) ,以便直到返回该单独的async方法后,才切换回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;
    }
}

在上面的示例中,除了对SetSlotCoordinates()的调用之外,异步代码会执行所有操作。 为此,它将调用包装在Action委托中,然后将其返回到UI线程,然后UI线程可以调用该线程。 当然,您不必返回Action委托; 那只是我看到的适应现有代码的最方便的方法。 您可以返回任何值或对象,并让UI线程适当地对其进行处理。

最后,对于上述所有内容,请注意,代码中没有任何地方明确依赖UI线程。 虽然我不确定您与单元测试有关的问题,但是上述内容应该更容易地适用于没有Dispatcher可用或由于某些原因而不想使用它的单元测试方案。

如果您想坚持使用Dispatcher ,那么您应该更确切地说明哪些地方不起作用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM