C#定时信号量同步两个线程和单个输出缓冲区

C# timed sempahore to syncronize two threads and a single output buffer

添加前言

在这里我想更好地解释一下我的应用场景。

我需要一个 windows 服务来将 SerialPort“转换”为 TCPPort。例如,假设我有一个串行票据打印机连接到原始 ascii 流的 COM 端口,我想通过网络的 TCP 套接字访问它。结果应该是串行打印机变成了网络打印机,我的服务应该link many tcp sockets to com port.

这是方案:

主要问题是 COM 端口有一个唯一的连接,但在这里我可以有许多来自网络客户端的同时连接。我需要同步写入 COMport 并从 COMport 获取输出并将其复制到所有连接的 TCP 客户端。

使用 TCP 连接时,我不知道写入流何时真正关闭,因为网络客户端可以在不关闭其连接的情况下发送打印作业,并在一段时间后发送另一个作业。 串行打印机是内联打印机,没有 start/end 命令,它可以简单地接收 ascii 字符,它们是按接收顺序排列的打印机。 这是因为我需要确保网络输入不会被混合,并且我想要一个计时器,它可以在释放同步写锁之前理解作业真的结束了。


原始问题

我有两个线程:AB

两个线程都必须通过 WriteToOutput() 方法写入单个输出缓冲区,我想确保如果 AB 想同时写入输出。 首先我需要一个简单的信号量:

private object locker = new object();

public void WriteToOutput(byte[] threadBuffer)
{
    lock (locker)
    {
        //... copy threadBuffer to outputBuffer
    }
}

但是我需要更安全地划分输出,因为线程可以清空其缓冲区,但它可以在锁释放后立即填充。

所以在并发的情况下如果线程A获得了锁,我想等待第二个线程B一段时间,让我们说一个 1s 的滴答声。如果此时线程A想再写点东西,它有优先权,B还要再等tick。如果A线程n个tick都没有写,那么它真的可以释放锁,B线程可以拿到锁。

仅供更正 - 那是监视器,不是信号量。

至于其余部分,这听起来像是一种奇怪的多线程设计,而且它会变得脆弱且不可靠。明确表示何时可以安全地释放共享资源 - 依赖任何类型的同步时间都是一个糟糕的主意。

问题是WriteToOutput方法显然不是同步的好点!如果您需要确保来自同一个线程的多个写入被序列化,您需要将同步点移到其他地方。或者,传递一个 Stream 而不是 byte[],并读取它直到它在锁内关闭 - 这将有效地做同样的事情,将责任转移给被调用者。只要确保你不会因为忘记关闭流而永远锁定它 :) 另一种选择是使用 BlockingCollection<byte[]>。当我们真的不知道你实际上 想做什么时,很难说什么是最好的选择。

编辑:

好吧,串口通信大概是我能想到的唯一正确使用时序的方法了。当然,在非实时系统上处理通信也可能有点棘手。

解决此问题的最佳方法是为您对串行端口的所有访问提供一个端点,该端点将处理通信 同步。无需从其他线程调用该方法,您只需 post 端点将读取的数据。但是,这需要您有一种识别其他线程的方法——我不确定您是否有类似的东西(也许是 TCP 套接字的 EndPoint?)。 最简单的 方法是使用 BlockingCollection:

private readonly object _syncObject = new object();
public void SendData(BlockingCollection<byte[]> data)
{
  lock (_syncObject)
  {
    byte[] buffer;

    while (data.TryTake(out buffer, TimeSpan.FromSeconds(1)))
    {
      // Send the data
    }
  }
}

这将继续从队列中读取和发送数据,只要它最多可以在第二个长周期内获得另一个缓冲区 - 如果花费超过一秒,该方法将退出并且另一个线程将有一个机会.

在套接字接收线程中,您将声明阻塞集合 - 这将根据您对接收代码的实现而有所不同。如果每个不同的套接字都有一个 some class 的实例,则可以将其声明为实例字段。如果没有,您可以使用 ThreadLocal这假设您正在使用手动线程,每个插槽一个线程 - 如果不是,您将需要不同的存储空间。

private readonly BlockingCollection<byte[]> _dataQueue = new BlockingCollection<byte[]>();

private void ReceiveHandler(byte[] data)
{
  // This assumes the byte array passed is already a copy
  _data.Add(data);
  SendData(_dataQueue);
}

这绝对不是处理这个问题的最佳方法,但它肯定是我现在能想到的最简单的方法——它几乎没有任何代码,而且它只使用 lockBlockingCollection .

我会看看 ReaderWriterLockSlim。

https://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim(v=vs.110).aspx