[英]Infinite producer/consumer via serial port data
I'm currently reading in data via a SerialPort
connection in an asynchronous Task
in a console application that will theoretically run forever (always picking up new serial data as it comes in).我目前正在控制台应用程序中的异步
Task
中通过SerialPort
连接读取数据,该应用程序理论上将永远运行(总是在新的串行数据传入时获取)。
I have a separate Task
that is responsible for pulling that serial data out of a HashSet
type that gets populated from my "producer" task above and then it makes an API request with it.我有一个单独的
Task
,负责从上面的“生产者”任务填充的HashSet
类型中提取串行数据,然后用它发出 API 请求。 Since the "producer" will run forever, I need the "consumer" task to run forever as well to process it.由于“生产者”将永远运行,我需要“消费者”任务也永远运行以处理它。
Here's a contrived example:这是一个人为的例子:
TagItems = new HashSet<Tag>();
Sem = new SemaphoreSlim(1, 1);
SerialPort = new SerialPort("COM3", 115200, Parity.None, 8, StopBits.One);
// serialport settings...
try
{
var producer = StartProducerAsync(cancellationToken);
var consumer = StartConsumerAsync(cancellationToken);
await producer; // this feels weird
await consumer; // this feels weird
}
catch (Exception e)
{
Console.WriteLine(e); // when I manually throw an error in the consumer, this never triggers for some reason
}
Here's the producer / consumer methods:这是生产者/消费者方法:
private async Task StartProducerAsync(CancellationToken cancellationToken)
{
using var reader = new StreamReader(SerialPort.BaseStream);
while (SerialPort.IsOpen)
{
var readData = await reader.ReadLineAsync()
.WaitAsync(cancellationToken)
.ConfigureAwait(false);
var tag = new Tag {Data = readData};
await Sem.WaitAsync(cancellationToken);
TagItems.Add(tag);
Sem.Release();
await Task.Delay(100, cancellationToken);
}
reader.Close();
}
private async Task StartConsumerAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
await Sem.WaitAsync(cancellationToken);
if (TagItems.Any())
{
foreach (var item in TagItems)
{
await SendTagAsync(tag, cancellationToken);
}
}
Sem.Release();
await Task.Delay(1000, cancellationToken);
}
}
I think there are multiple problems with my solution but I'm not quite sure how to make it better.我认为我的解决方案存在多个问题,但我不太确定如何让它变得更好。 For instance, I want my "data" to be unique so I'm using a
HashSet
, but that data type isn't concurrent-friendly so I'm having to lock with a SemaphoreSlim
which I'm guessing could present performance issues with large amounts of data flowing through.例如,我希望我的“数据”是唯一的,所以我使用的是
HashSet
,但该数据类型不是并发友好的,所以我不得不使用SemaphoreSlim
锁定,我猜这可能会出现性能问题大量数据流过。
I'm also not sure why my catch
block never triggers when an exception is thrown in my StartConsumerAsync
method.我也不确定为什么在我的
StartConsumerAsync
方法中抛出异常时我的catch
块永远不会触发。
Finally, are there better / more modern patterns I can be using to solve this same problem in a better way?最后,是否有更好/更现代的模式可以用来以更好的方式解决同样的问题? I noticed that
Channels
might be an option but a lot of producer/consumer examples I've seen start with a producer having a fixed number of items that it has to "produce", whereas in my example the producer needs to stay alive forever and potentially produces infinitely.我注意到
Channels
可能是一个选项,但我见过的很多生产者/消费者示例都是从生产者开始,它必须“生产”固定数量的项目,而在我的示例中,生产者需要永远活着并且可能无限地产生。
First things first, starting multiple asynchronous operations and awaiting them one by one is wrong:首先,启动多个异步操作并一个一个地等待它们是错误的:
// Wrong
await producer;
await consumer;
The reason is that if the first operation fails, the second operation will become fire-and-forget.原因是如果第一个操作失败,第二个操作将变成即发即弃。 And allowing tasks to escape your supervision and continue running unattended, can only contribute to your program's instability.
允许任务逃脱你的监督并继续无人看管运行,只会导致你的程序不稳定。 Nothing good can come out from that.
没有什么好处可以从中产生。
// Correct
await Task.WhenAll(producer, consumer)
Now regarding your main issue, which is how to make sure that a failure in one task will cause the timely completion of the other task.现在关于您的主要问题,即如何确保一项任务的失败会导致另一项任务的及时完成。 My suggestion is to hook the failure of each task with the cancellation of a
CancellationTokenSource
.我的建议是通过取消
CancellationTokenSource
挂钩每个任务的失败。 In addition, both tasks should watch the associated CancellationToken
, and complete cooperatively as soon as possible after they receive a cancellation signal.此外,两个任务都应该监视关联的
CancellationToken
,并在收到取消信号后尽快合作完成。
var cts = new CancellationTokenSource();
Task producer = StartProducerAsync(cts.Token).OnErrorCancel(cts);
Task consumer = StartConsumerAsync(cts.Token).OnErrorCancel(cts);
await Task.WhenAll(producer, consumer)
Here is the OnErrorCancel
extension method:这是
OnErrorCancel
扩展方法:
public static Task OnErrorCancel(this Task task, CancellationTokenSource cts)
{
return task.ContinueWith(t =>
{
if (t.IsFaulted) cts.Cancel();
return t;
}, default, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap();
}
Instead of doing this, you can also just add an all-enclosing try
/ catch
block inside each task, and call cts.Cancel()
in the catch
.除了这样做,您还可以在每个任务中添加一个完全封闭的
try
/ catch
块,并在catch
中调用cts.Cancel()
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.