Асинхронное чтение в приложении SerialPort WPF - PullRequest
0 голосов
/ 11 марта 2019

Я делаю приложение 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());
                }
            }
        }

А это, мой «Закрыть сервер»"button button:

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.Всякий раз, когда я пытаюсь отменить задачу, это происходит не сразу. Мне нужно попытаться отправить другую команду, чтобы приложение узнало, что был запрошен токен отмены, и затем оно закрывает порт.Я попытался закрыть порт в своей команде close server, но затем выдает 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 голосов
/ 11 марта 2019

Вы можете ввести метод расширения, чтобы дождаться отмены или задание завершено.

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() не был отменен правильно, и изучил исходный код . И метод только один раз проверяет, должна ли операция быть отменена

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