C#串口通信中的await事件和超时
Posted
技术标签:
【中文标题】C#串口通信中的await事件和超时【英文标题】:C# await event and timeout in serial port communication 【发布时间】:2018-11-16 10:17:21 【问题描述】:您好,我在串口上有一个简单的通信,一切都根据书和文档,所以开放端口方法如下所示:
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)
但它看起来很丑。
所以我认为我的基本问题是如何有效地等待事件响应并获得超时?
【问题讨论】:
while (!isFinished)
ManualEventResetSlim ...更多的是Wait(timeout)
...
异常处理是我最讨厌的事情,而你的处理并不理想。对于初学者,您将在发生致命异常后继续操作,并且没有正确记录/公开内容。这里有两篇关于我经常链接的材料的文章:blogs.msdn.microsoft.com/ericlippert/2008/09/10/… | codeproject.com/Articles/9538/…
将 isFinished
从 bool
更改为 ManualEventResetSlim
就像 ManualEventResetSlim isFinished=ManualEventResetSlim ();
而不是将其设置为 false/true
使用 Reset/Set
然后改为 await worker
使用 isFinished.Wait(Timeout)
你知道需要多少字节来响应吗?
原来我的原始答案完全错误,因为SerialPort
异步 API 完全忽略了超时属性。请检查更新。
【参考方案1】:
写操作后循环读取数据,直到获得完整响应。但是您需要使用同步 API 和 Task.Run()
,因为当前版本的异步 API 完全忽略了 SerialPort
超时属性,而在基于任务的 API 中几乎完全忽略了 CancellationToken
。
摘自与SerialPort.BaseStream.ReadAsync()
相关的SerialPort.ReadTimeout Microsoft Docs,因为它使用默认实现Stream.ReadAsync()
:
此属性不影响BaseStream 属性返回的流的BeginRead 方法。
使用同步 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
方法可以调用来取消操作。
更多信息请查看:
Top 5 SerialPort Tips Kim Hamilton 的文章 .NET GitHub 上的Recommended asynchronous usage pattern of SerialPort、Document that CancellationToken in Stream.ReadAsync() is advisory 和NetworkStream.ReadAsync/WriteAsync ignores CancellationToken 相关问题 Should I expose asynchronous wrappers for synchronous methods?Stephen Toub 的文章【讨论】:
以上是关于C#串口通信中的await事件和超时的主要内容,如果未能解决你的问题,请参考以下文章