C#串口通信中的await事件和超时 [英] C# await event and timeout in serial port communication

查看:15
本文介绍了C#串口通信中的await事件和超时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

您好,我在串口上有一个简单的通信,一切都根据书籍和文档,所以开放端口方法如下所示:

Hi I have a simple communication on serial port well all is according to book and documentation so open port method looks like this:

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

这里我们有一个关于数据读取的事件:

Here we have an event on data read:

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

一切都很好,花花公子,但现在我想按需发送消息并将响应存储在属性中,我还想在该任务超时时添加取消令牌.所以我想出了这个方法:

Well all is fine and dandy, but now i want to send a message on demand and store response in a property, also i want to add cancellation token on that task timeout. So i came up with this method:

        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循环,如果它是任务,它会进入无限循环,并且它不会捕获超时令牌,如果我将它放在任务之外并删除工作人员它可以工作,但我会丢失取消令牌.我想我可以做一些手动倒计时,比如:

Problem is with this while loop, if it is task it goes into endless loop, and it does not capture timeout token, if i put it outside a task and remove worker it works but im loosing cancellation token. I guess i could do some manual countdown like:

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

但它看起来很丑.

所以我认为我的基本问题是如何有效地等待事件响应并获得超时?

So i think my basic question is how to effectively await for event to response and get a timeout?

推荐答案

写操作后循环读取数据,直到得到完整响应.但是您需要使用同步 API 和 Task.Run() 因为当前版本的异步 API 完全忽略了 SerialPort 超时属性和 Task 中的 CancellationToken几乎完全基于 API.

Read data in a loop after write operation until get a full response. But you need to use synchronous API and Task.Run() as current version of the asynchronous API ignores SerialPort timeout properties completely and CancellationToken in Task based API almost completely.

摘自 SerialPort.ReadTimeoutSerialPort.BaseStream.ReadAsync() 相关的 Microsoft Docs,因为它使用默认实现 Stream.ReadAsync():

Excerpt from the SerialPort.ReadTimeout Microsoft Docs that is relevant to SerialPort.BaseStream.ReadAsync() because it uses default implementation Stream.ReadAsync():

此属性不影响 BeginRead 方法view=netframework-4.7.2" rel="nofollow noreferrer">BaseStream 属性.

This property does not affect the BeginRead method of the stream returned by the BaseStream property.

使用同步 API 和动态超时属性更新的示例实现:

Example implementation using synchronous API and dynamic timeout properties update:

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 方法可以调用来取消操作.

You can do similar thing with CancellationToken instance and use CancellationToken.ThrowIfCancellationRequested() between read and write operations but you have to make sure that proper timeouts are set on SerialPort or otherwise Thread pool thread will hang forever possible holding a lock. As far as I know you can't utilize CancellationToken.Register() because there is no SerialPort method to call to cancel an operation.

更多信息请查看:

  • Top 5 SerialPort Tips article by Kim Hamilton
  • Recommended asynchronous usage pattern of SerialPort, Document that CancellationToken in Stream.ReadAsync() is advisory and NetworkStream.ReadAsync/WriteAsync ignores CancellationToken related issues on .NET GitHub
  • Should I expose asynchronous wrappers for synchronous methods? article by Stephen Toub

这篇关于C#串口通信中的await事件和超时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆