听起来生产者 - 消费者设计可能非常适合您的问题。一般而言,客户端线程会将任何接收到的数据放入(线程安全)队列中,之后不会对其进行修改 - 任何到达的新数据都将进入队列中的新插槽。然后,主线程可以等待任何队列中的新数据,并在它到达后对其进行处理。主线程可以定期检查所有队列,或者(更好地)在数据放入队列时接收某种通知,以便它可以在没有任何事情发生时休眠并且不会占用 CPU 时间。
由于您询问锁:这是一个基本的基于锁的实现,作为队列的替代方案,也许这将有助于您理解原理
class IncomingClientData
{
private List<byte> m_incomingData = new List<byte>();
private readonly object m_lock = new object();
public void Append(IEnumerable<byte> data)
{
lock(m_lock)
{
m_incomingData.AddRange(data);
}
}
public List<byte> ReadAndClear()
{
lock(m_lock)
{
List<byte> result = m_incomingData;
m_incomingData = new List<byte>();
return result;
}
}
}
在此示例中,您的客户端线程将Append
使用它们接收到的数据进行调用,并且主线程可以通过调用收集自上次检查以来到达的所有接收到的数据ReadAndClear
。
通过将两个函数中的所有代码锁定为线程安全的m_lock
,这只是一个普通的普通对象 - 您可以锁定 C# 中的任何对象,但我相信这可能会令人困惑,如果不小心使用,实际上会导致细微的错误,所以我几乎总是使用一个专用的对象来锁定。一次只有一个线程可以持有一个对象的锁,因此这些函数的代码一次只能在一个线程中运行。例如,如果您的主线程ReadAndClear
在客户端线程仍在忙于将数据附加到列表时调用,则主线程将等待,直到客户端线程离开该Append
函数。
不需要为此创建一个新类,但它可以防止意外,因为我们可以仔细控制共享状态的访问方式。例如,我们知道返回内部列表是安全的,ReadAndClear()
因为当时不能有其他对同一列表的引用。
现在你的第二个问题:只是简单地调用一个方法不会导致该方法在不同的线程上运行,无论该方法在哪个类中。Invoke
是 WinForms UI 线程的一个特殊功能,你必须实现如果你想Invoke
在你的工作线程中做一些事情,你自己的功能。在内部,Invoke
通过将您想要运行的代码放入应该在 UI 线程上运行的所有事物的队列中,包括 UI 事件。UI 线程本身基本上是一个循环,它总是从该队列中提取下一项工作,然后执行该工作,然后从队列中获取下一项,依此类推。这也是为什么您不应该在事件处理程序中做长时间工作的原因——只要 UI 线程忙于运行您的代码,它就无法处理其队列中的下一个项目,因此您将搁置所有发生的其他工作项/事件。
如果您希望您的客户端线程运行某个功能,您必须实际提供代码 - 例如让客户端线程检查一些队列以获取来自主线程的命令。