C#在串行端口通信中等待事件和超时 [英] C# await event and timeout in serial port communication

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

问题描述

我在串行端口上的通讯很简单,所有内容都是根据书籍和文档而定,因此开放端口方法如下:

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 超时属性完全基于任务的API中的 CancellationToken

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.ReadTimeout SerialPort.BaseStream.ReadAsync()相关的Microsoft文档它使用默认实现 Stream.ReadAsync()

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


此属性不影响 BeginRead 方法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.

有关更多信息,请检查:

For more information check:

  • 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#在串行端口通信中等待事件和超时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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