C # ожидание события и тайм-аута в последовательном порту связи - PullRequest
0 голосов
/ 16 ноября 2018

Привет, у меня простая связь по последовательному порту, все в соответствии с книгой и документацией, поэтому метод открытого порта выглядит следующим образом:

    public SerialPort OpenPort(string portName)
    {
        Port = new SerialPort(portName, BaudRate);
        try
        {
            Port.Open();
            Port.DtrEnable = true;
            Port.RtsEnable = true;

            Port.DataReceived += DataReceivedEvent;
        }
        catch (Exception e)
        {
            Console.WriteLine($"ERRROR: {e.Message}");
        }

        return Port;
    }

Здесь у нас есть событие для чтения данных:

    private async void DataReceivedEvent(object sender, SerialDataReceivedEventArgs e)
    {
        var data = new byte[Port.BytesToRead];
        await Port.BaseStream.ReadAsync(data, 0, data.Length);

        Response = data;

        isFinished = true;
    }

Ну, все в порядке, но теперь я хочу отправить сообщение по требованию и сохранить ответ в свойстве, а также добавить токен отмены по таймауту этой задачи.Итак, я придумал этот метод:

        public async Task SendMessenge(byte[] messange)
    {
        var cancellationTokenSource = new CancellationTokenSource();
        CancellationToken token = cancellationTokenSource.Token;
        cancellationTokenSource.CancelAfter(5000);
        token.ThrowIfCancellationRequested();

        isFinished = false;
        try
        {
            Task worker = Task.Run(() =>
            {
                while (!isFinished)
                {
                }
            }, token);

            await Port.BaseStream.WriteAsync(messange, 0, messange.Length, token);
            await worker;
        }
        catch (OperationCanceledException e)
        {
            throw new OperationCanceledException(e.Message, e, token);
        }
    }

Проблема в том, что цикл while, если это задача, переходит в бесконечный цикл, и он не захватывает токен тайм-аута, если я помещаю его вне задачи иудалить работника это работает, но я теряю токен отмены.Я думаю, что мог бы сделать какой-то ручной обратный отсчет, например:

double WaitTimeout = Timeout + DateAndTime.Now.TimeOfDay.TotalMilliseconds;
while (!(DateAndTime.Now.TimeOfDay.TotalMilliseconds >= WaitTimeout)|| !isFalse) 

Но это выглядит ужасно.

Так что я думаю, что мой основной вопрос заключается в том, как эффективно ожидать события от ответа и получить тайм-аут?

1 Ответ

0 голосов
/ 05 декабря 2018

Чтение данных в цикле после операции записи до получения полного ответа. Но вам нужно использовать синхронный API и Task.Run(), поскольку текущая версия асинхронного API полностью игнорирует свойства SerialPort timeout и CancellationToken в API на основе задач почти полностью.

Выдержка из SerialPort.ReadTimeout Документы Microsoft, относящиеся к SerialPort.BaseStream.ReadAsync(), поскольку в нем используется реализация по умолчанию Stream.ReadAsync():

Это свойство не влияет на метод BeginRead потока, возвращаемого свойством BaseStream .

Пример реализации с использованием синхронного API и обновление свойств динамического тайм-аута:

static byte[] SendMessage(byte[] message, TimeSpan timeout)
{
    // Use stopwatch to update SerialPort.ReadTimeout and SerialPort.WriteTimeout 
    // as we go.
    var stopwatch = Stopwatch.StartNew();

    // Organize critical section for logical operations using some standard .NET tool.
    lock (_syncRoot)
    {
        var originalWriteTimeout = _serialPort.WriteTimeout;
        var originalReadTimeout = _serialPort.ReadTimeout;
        try
        {
            // Start logical request.
            _serialPort.WriteTimeout = (int)Math.Max((timeout - stopwatch.Elapsed).TotalMilliseconds, 0);
            _serialPort.Write(message, 0, message.Length);

            // Expected response length. Look for the constant value from 
            // the device communication protocol specification or extract 
            // from the response header (first response bytes) if there is 
            // any specified in the protocol.
            int count = ...;
            byte[] buffer = new byte[count];
            int offset = 0;
            // Loop until we recieve a full response.
            while (count > 0)
            {
                _serialPort.ReadTimeout = (int)Math.Max((timeout - stopwatch.Elapsed).TotalMilliseconds, 0);
                var readCount = _serialPort.Read(buffer, offset, count);
                offset += readCount;
                count -= readCount;
            }
            return buffer;
        }
        finally
        {
            // Restore SerialPort state.
            _serialPort.ReadTimeout = originalReadTimeout;
            _serialPort.WriteTimeout = originalWriteTimeout;
        }
    }
}

И пример использования:

byte[] request = ...;
TimeSpan timeout = ...;

var sendTask = Task.Run(() => SendMessage(request, timeout));
try
{
    await await Task.WhenAny(sendTask, Task.Delay(timeout));
}
catch (TaskCanceledException)
{
    throw new TimeoutException();
}
byte[] response = await sendTask;

Вы можете сделать то же самое с экземпляром CancellationToken и использовать CancellationToken.ThrowIfCancellationRequested() между операциями чтения и записи, но вы должны убедиться, что для SerialPort установлены правильные таймауты, иначе поток пула потоков будет зависать всегда, удерживая блокировку , Насколько я знаю, вы не можете использовать CancellationToken.Register(), потому что нет метода SerialPort, который можно было бы вызвать для отмены операции.

Для получения дополнительной информации проверьте:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...