[英]How to pass data between threads?
在.NET中的线程之间传递数据的方法是什么? 我目前可以想到两件事:
.NET Framework有哪些解决方案来解决这个问题。 也许.NET已经实现了通用的生产者 - 消费者模式? 也许我可以以某种方式使用Thread.GetData和Thread.SetData?
作为Ash解决方案的替代方案,请考虑以下示例。
假设您有两个线程 - 一个用于接收来自套接字的数据包,另一个用于处理这些数据包。 显然,当数据包可用于处理时,Receiver线程需要通知处理器线程,因此需要以某种方式在线程之间共享数据包。 我通常使用共享数据队列执行此操作。
同时,我们不一定要将线程紧密耦合在一起。 例如,Receiver线程甚至不应该知道处理器线程存在。 接收方需要关注的是从网络接收数据包,然后通知任何感兴趣的用户数据包可用于处理。 事件是在.NET中实现这一目标的完美方式。
所以这里有一些代码。
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
public class Packet
{
public byte[] Buffer { get; private set; }
public Packet(byte[] buffer)
{
Buffer = buffer;
}
}
public class PacketEventArgs : EventArgs
{
public Packet Packet { get; set; }
}
public class UdpState
{
public UdpClient Client{get;set;}
public IPEndPoint EndPoint{get;set;}
}
public class Receiver
{
public event EventHandler<PacketEventArgs> PacketReceived;
private Thread _thread;
private ManualResetEvent _shutdownThread = new ManualResetEvent(false);
public void Start() { _thread.Start(); }
public void Stop() { _shutdownThread.Set(); }
public Receiver()
{
_thread = new Thread(
delegate() {
// Create the client UDP socket.
IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 5006);
UdpClient client = new UdpClient( endPoint );
// Receive the packets asynchronously.
client.BeginReceive(
new AsyncCallback(OnPacketReceived),
new UdpState() { Client = client, EndPoint = endpoint });
// Wait for the thread to end.
_shutdownThread.WaitOne();
}
);
}
private void OnPacketReceived(IAsyncResult ar)
{
UdpState state = (UdpState)ar.AsyncState;
IPEndPoint endPoint = state.EndPoint;
byte[] bytes = state.Client.EndReceive(ar, ref endPoint);
// Create the packet.
Packet packet = new Packet(bytes);
// Notify any listeners.
EventHandler<PacketEventArgs> handler = PacketReceived;
if (handler != null) {
handler(this, new PacketEventArgs() { Packet = packet });
}
// Read next packet.
if (!_shutdownThread.WaitOne(0)) {
state.Client.BeginReceive(
new AsyncCallback(OnPacketReceived),
state);
}
}
}
public class Processor
{
private Thread _thread;
private object _sync = new object();
private ManualResetEvent _packetReceived = new ManualResetEvent(false);
private ManualResetEvent _shutdownThread = new ManualResetEvent(false);
private Queue<Packet> _packetQueue = new Queue<Packet>(); // shared data
public void Start() { _thread.Start(); }
public void Stop() { _shutdownThread.Set(); }
public Processor()
{
_thread = new Thread(
delegate() {
WaitHandle[] handles = new WaitHandle[] {
_shutdownThread,
_packetReceived
};
while (!_shutdownThread.WaitOne(0)) {
switch (WaitHandle.WaitAny(handles)) {
case 0: // Shutdown Thread Event
break;
case 1: // Packet Received Event
_packetReceived.Reset();
ProcessPackets();
break;
default:
Stop();
break;
}
}
}
);
}
private void ProcessPackets()
{
Queue<Packet> localPacketQueue = null;
Queue<Packet> newPacketQueue = new Queue<Packet>();
lock (_sync) {
// Swap out the populated queue with the empty queue.
localPacketQueue = _packetQueue;
_packetQueue = newPacketQueue;
}
foreach (Packet packet in localPacketQueue) {
Console.WriteLine(
"Packet received with {0} bytes",
packet.Buffer.Length );
}
}
public void OnPacketReceived(object sender, PacketEventArgs e)
{
// NOTE: This function executes on the Receiver thread.
lock (_sync) {
// Enqueue the packet.
_packetQueue.Enqueue(e.Packet);
}
// Notify the Processor thread that a packet is available.
_packetReceived.Set();
}
}
static void Main()
{
Receiver receiver = new Receiver();
Processor processor = new Processor();
receiver.PacketReceived += processor.OnPacketReceived;
processor.Start();
receiver.Start();
Thread.Sleep(5000);
receiver.Stop();
processor.Stop();
}
我知道那里要消化很多。 该程序应该在.NET 3.5中工作,只要您在端口5006上有UDP流量。
就线程之间的数据共享而言,兴趣点是Processor类的ProcessPackets()和OnPacketReceived()方法。 请注意,OnPacketReceived()方法在Receiver线程上发生,即使该方法是Processor类的一部分,并且使用同步对象同步队列。
虽然它不是内置解决方案,但您可以创建包含私有“同步”对象的类。 然后,在属性和方法调用中,使用同步对象上的lock语句来确保序列化访问。
例如:
class DataClass{
private object m_syncObject=new object();
private string m_data;
public string Data
{
get{
lock(m_syncobject)
{
return m_data;
}
}
set{
lock(m_syncobject)
{
m_data=value;
}
}
}
}
在一个线程上创建DataClass()的实例,然后将此实例传递给第二个或更多线程。 需要时访问线程安全的Data属性以在线程之间传递/接收数据。
看看这里 ,其中一些回答可能会回答你的问题。
至于Ash的解决方案:这种“线程安全”数据类(我称之为“伪线程安全”)的问题,特别是如果它们有不同的成员,这些成员可能会在线程安全调用之间发生变化。 这适用于所有多成员类,但在所有枚举(列表,数组)中尤其是一个问题,因为它使得像“.Count”这样的函数几乎不可能使用(google了解详情)。
例:
class pseudoThreadsafeHuman{
private object m_syncobject;
public string firstName;
public string lastName;
public string fullName
get{
lock(m_syncobject)
{
return lastName & "," & firstName;
}
}
set{
lock(m_syncobject)
{
lastName = value.Split(",")[1];
firstName = value.Split(",")[2];
}
}
}
在这里有人可能会尝试使用这样的东西:
public void isJohn(pseudoThreadSafeHuman currentPerson) {
if currentPerson.firstName == "John"
{
MessageBox.Show(currentPerson.fullName)
}
}
成员firstName,lastName和fullName都是线程安全的。 由于if和MessageBox.Show()之间的值可能会发生变化,因此这可能会打印除“John”之外的其他内容。 另一个例子:
getInitials(pseudoThreadSafeHuman currentPerson)之类的东西可能会抛出异常:
public getInitials(pseudoThreadSafeHuman currentPerson)
string initials = ""
if currentPerson.firstName != "" {
initials += currentPerson.firstName[0]; // crash here if firstName got changed to ""
}
if currentPerson.lastName != "" {
initials += currentPerson.lastName[0]; // crash here if lastName got changed to ""
}
}
这是错误的代码使用的愚蠢的例子。 另外我不太了解C#(我自己使用的是VB.Net),因此语法可能完全不合适。 我猜你还是明白了。 因此在我看来,线程安全类会导致编程错误,而只是使用经典的synclock(对其他程序员来说也更具可读性)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.