簡體   English   中英

SerialPort WPF應用程序中的異步讀取

[英]Asynchronous Read in SerialPort WPF application

我正在做一個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).

您可以引入擴展方法來等待取消或任務完成。

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:解決了一個問題,即如果未取消CancellationTokenWaitForCancel()函數產生的任務將永遠無法完成。

Edit3:調查了為什么ReadAsync()無法正確取消並查看源代碼的原因 方法僅檢查一次是否應該取消操作

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);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM