C#timed sempahore同步两个线程和一个输出缓冲区

时间:2015-07-09 15:29:35

标签: c# multithreading timer semaphore

添加前言

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

我需要一个Windows服务来转换"将SerialPort转换为TCPPort。例如,假设我有一个串行票据打印机连接到原始ascii流的COM端口,我想通过网络上的TCP套接字访问它。结果应该是串行打印机成为网络打印机,我的服务应该将许多tcp套接字链接到com端口。

这是方案: enter image description here

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

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

原始问题

我有两个主题: A B

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

private object locker = new object();

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

但我需要更安全一点来划分输出,因为一个线程可以清空它的缓冲区,但它可以在锁定释放后立即填充。

所以在并发的情况下如果线程 A 获得锁定,我想等待第二个线程 B for while,让我们说一下1秒。如果此时线程 A 想要写更多内容,则它具有优先级,而 B 必须等待另一个时钟。如果线程 A 没有为整个刻度线写入,那么它可以真正重新锁定, B 线程可以获得锁定。

2 个答案:

答案 0 :(得分:3)

只是为了纠正 - 这是一个监视器,而不是一个信号量。

至于其余的,这听起来像一个奇怪的多线程设计,它会变得脆弱和不可靠。释放共享资源的安全性显而易见 - 依赖任何类型的同步时机都是一个糟糕的主意。

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

修改

好的,串口通信是关于我能想到的唯一正确使用的时序。当然,处理非实时系统上的通信也有点棘手。

解决此问题的最佳方法是为所有访问串行端口提供单个端点,以处理通信同步。您只需发布端点可读取的数据,而不是从其他线程调用该方法。但是,这要求您有一种方法来识别其他线程 - 我不确定您是否有类似的东西(可能是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
    }
  }
}

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

在套接字接收线程中,您将声明阻塞集合 - 这将根据您对接收代码的实现而有所不同。如果每个不同的套接字都有一个类的单个实例,则可以将其声明为实例字段。如果没有,您可以使用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

答案 1 :(得分:0)