【问题标题】:C# timed sempahore to syncronize two threads and a single output bufferC# 定时信号量同步两个线程和单个输出缓冲区
【发布时间】:2015-07-09 15:29:35
【问题描述】:

添加前言

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

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

这是方案:

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

使用 TCPconnections 我无法知道写入流何时真正关闭,因为网络客户端可以在不关闭其连接的情况下发送打印作业,并在一段时间后发送另一个作业。 串行打印机是内联打印机,没有开始/结束命令,它可以简单地接收 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 必须等待另一个滴答声。如果线程A没有写n个完整的tick,那么它真的可以释放锁,B线程可以获得锁。

【问题讨论】:

  • 这里的多线程有什么意义?看起来您只需要异步 I/O。你想做什么?

标签: c# multithreading timer semaphore


【解决方案1】:

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

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

问题是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

【讨论】:

  • 我添加了一个前言来更好地解释我的场景,请考虑一下,如果您同意定时监视器是唯一且更好的选择,请告诉我。谢谢!
  • 我想这就是我要找的。谢谢
  • 我有点怀疑...我不应该同步队列追加而不是队列获取吗?也许我必须使用 TryAdd() tcpsocket 线程而不是 serialsend 线程?
  • @Tobia 不是真的,在这个模型中。每个套接字都有一个单独的阻塞集合,因此一次只能阻塞一个套接字源。否则,您将不得不一次读取所有队列,这最终会让您获得一个不错的多生产者 -> 单一消费者模式 - 这是我提到的最好的方式,但这远非易事。您几乎必须从头开始,理想情况下保持 100% 异步。不过,它会节省延迟和资源。
  • @Tobia 请注意,您在测试代码中的同一线程上运行 TcpReadSyncAndSend - 这意味着无论如何您都不会有机会从同一个套接字接收更多数据.您至少需要做一些类似ThreadPool.QueueUserWorkItem(() =&gt; SyncAndSend(queue)); 的事情。这当然有点浪费线程,但是如果不使整个代码异步,或者使用专用线程和队列队列一起发送,基本上就无法避免这种情况。
【解决方案2】:
猜你喜欢
  • 2023-04-02
  • 1970-01-01
  • 2020-07-08
  • 1970-01-01
  • 2019-03-18
  • 2019-06-05
  • 1970-01-01
  • 2015-07-06
  • 1970-01-01
相关资源
最近更新 更多