SerialPort WPF应用程序中的异步读取

时间:2019-03-11 10:18:44

标签: c# wpf asynchronous async-await serial-port

我正在做一个WPF应用程序,该应用程序从USB LoRaWAN加密狗读取信息并向其中发送消息。
以下是该应用程序应做的快速恢复:

使用此应用程序,用户应该能够连接“服务器”,也就是说,打开串行端口并开始使用消息(目前,我只是试图在控制台上显示它们)。为此,用户单击菜单选项。
然后,用户还应该能够将消息或“命令”发送到加密狗。发生这种情况时,加密狗会发送“已收到消息”信号(也以消息的形式)。
最后,用户还必须能够通过另一个菜单选项关闭服务器。
每条消息都必须以换行符结尾。

这是我的“启动服务器”按钮单击命令和读取异步命令的代码:

private async void ConnectServerCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
#if DEBUG
            Debug.Print("ConnectServer");
#endif
            cts = new CancellationTokenSource();
            //CancellationToken ct = cts.Token;

            // Se llama al método OpenPort() de MySerialPort que intenta abrir el puerto, hace un SET del VERBOSITY y controla las excepciones
            MySerialPort.OpenPort();

            await ConnectServer(cts.Token);
        }

        private async Task ConnectServer(CancellationToken ct)
        {
            // ENTRAR AL BUCLE PARA RECIBIR/ENVIAR MENSAJES
            byte[] recBuffer = new byte[1024];
            int result;

            while ((!ct.IsCancellationRequested) && (MySerialPort.SerialPort.IsOpen))
            {
                try
                {
                    result = await MySerialPort.SerialPort.BaseStream.ReadAsync(recBuffer, 0, 1024, ct);
                    if (result > 0)
                    {
                        Debug.Print("{0} - {1} ", DateTime.Now, Encoding.Default.GetString(recBuffer));
                    }
                    Debug.Print("IsCancellationRequested: {0}", ct.IsCancellationRequested);

                    /*if (Encoding.Default.GetString(recBuffer).Contains("\n")) {
                        msg = (Encoding.Default.GetString(recBuffer)).Split('\n')[0];
                        return msg;
                    }*/

                    if (ct.IsCancellationRequested)
                    {
                        MySerialPort.ClosePort();
                        return;
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error in ReadSerialBytesAsync: " + ex.ToString());
                }
            }
        }

这是我的“关闭服务器”按钮命令:

private void DisconnectServerCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
#if DEBUG
            Debug.Print("DisconnectServer");
#endif
            // ROMPER EL BUCLE DE CONNECTSERVER
            if (cts != null)
            {
                cts.Cancel();
            }

        }

目前,我能够连接服务器并开始异步读取一些消息,但是目前有两个问题。

1。每当我尝试取消任务时,它都不会立即发生。我必须尝试向应用发送另一个命令,以了解请求了取消令牌,然后它确实关闭了端口。我试图在我的关闭服务器命令中关闭端口,但随后抛出IOException,我不知道这是否是正确的方法。
  2。我多次收到消息。每当我发送命令时,加密狗都会发送接收到的信号超过一次。我不明白为什么会这样。

我也只想在找到换行符后才处理消息,但是我仍然不明白这样做的逻辑。

这是示例输出:

ConnectServer
Trying to open SerialPort COM3.
03/11/2019 11:14:30 - RIsCancellationRequested: False
03/11/2019 11:14:30 - SET 0 SUCCESS VERBOSE=LONG,DEVPORT,OFF,OFF
IsCancellationRequested: False
LoadDeviceListFile
03/11/2019 11:14:32 - RET 0 SUCCESS VERBOSE=LONG,DEVPORT,OFF,OFF
IsCancellationRequested: False
03/11/2019 11:14:32 - GET 0 SUCCESS DEV_PROV_LIST=\
0,70B3D5E75E00IsCancellationRequested: False
03/11/2019 11:14:32 - 4ET 0 SUCCESS DEV_PROV_LIST=\
0,70B3D5E75E00IsCancellationRequested: False
03/11/2019 11:14:32 - 275,ABP,C,0,00004275,2B7E151628AED2A6ABF7158809CF4F3C,2B7E151628AED2A6ABF7158809CF4F3C,70B3D5E75F600000,2B7E151628AED2A6ABF7158809CF4F3CIsCancellationRequested: False
03/11/2019 11:14:32 - 
75,ABP,C,0,00004275,2B7E151628AED2A6ABF7158809CF4F3C,2B7E151628AED2A6ABF7158809CF4F3C,70B3D5E75F600000,2B7E151628AED2A6ABF7158809CF4F3CIsCancellationRequested: False
GetFromDeviceCommand
COMPort de MySerialPort: COM3
03/11/2019 11:14:38 - R75,ABP,C,0,00004275,2B7E151628AED2A6ABF7158809CF4F3C,2B7E151628AED2A6ABF7158809CF4F3C,70B3D5E75F600000,2B7E151628AED2A6ABF7158809CF4F3CIsCancellationRequested: False
03/11/2019 11:14:38 - GET 0 SUCCESS FIRMWARE_INFO=LWCServer:0.3(beta), Kernel:3.4.0.3924,\
FWName:3.4.0.3924.lwc-server.lwcs.ClassC.lrctm.EU.chkpt.wdt2-dfp-br-F5437A
IsCancellationRequested: False
DisconnectServer
GetFromDeviceCommand
COMPort de MySerialPort: COM3
03/11/2019 11:14:44 - RET 0 SUCCESS FIRMWARE_INFO=LWCServer:0.3(beta), Kernel:3.4.0.3924,\
FWName:3.4.0.3924.lwc-server.lwcs.ClassC.lrctm.EU.chkpt.wdt2-dfp-br-F5437A
IsCancellationRequested: True
SerialPort COM3 open. Closing it.
The thread 0x3b14 has exited with code 0 (0x0).
The program '[10580] LWCConfig_02.exe' has exited with code 0 (0x0).

1 个答案:

答案 0 :(得分:0)

您可以引入扩展方法来等待取消或 任务完成。

public static async Task<T> WhenFinishedOrCancelled<T>(this Task<T> task, CancellationToken cancellationToken)
{
    var tcs = new TaskCompletionSource<byte>();

    using (cancellationToken.Register(s => (s as TaskCompletionSource<byte>).TrySetResult(1), tcs))
    {
        if (tcs.Task == await Task.WhenAny(task, tcs.Task))
        {
            throw new OperationCanceledException(cancellationToken);
        }

        tcs.SetCanceled();
    }

    return await task;
}

然后像这样使用它

while(...)
{
    try
    {
        var readTask = await <...>.ReadAsync(...)
                            .WhenFinishedOrCancelled(ct);
    }
}

编辑:感谢@PauloMorgado提供了改进的WaitForCancel()功能。

Edit2:解决了一个问题,如果未取消WaitForCancel(),则CancellationToken函数产生的任务将永远无法完成。

Edit3:调查了ReadAsync()未正确取消的原因,并调查了source code。而且方法只检查一次是否应该取消操作

public virtual Task<int> ReadAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
    // If cancellation was requested, bail early with an already completed task.
    // Otherwise, return a task that represents the Begin/End methods.
    return cancellationToken.IsCancellationRequested
                ? Task.FromCancellation<int>(cancellationToken)
                : BeginEndReadAsync(buffer, offset, count);
}