简体   繁体   English

无限生产者/消费者通过串口数据

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM