简体   繁体   English

如何在线程之间传递数据?

[英]How to pass data between threads?

What are ways to pass data between threads in .NET? 在.NET中的线程之间传递数据的方法是什么? There are two things I can currently think of: 我目前可以想到两件事:

  1. Membervariables, eg using the producer-consumer-queue pattern. 成员变量,例如使用producer-consumer-queue模式。
  2. Using the ParameterizedThreadStart delegate when starting the the thread. 在启动线程时使用ParameterizedThreadStart委托。 (Only works once, not good for long running background worker threads). (仅适用于一次,不适合长时间运行的后台工作线程)。

What facitlites does the .NET Framework have to solve this problem. .NET Framework有哪些解决方案来解决这个问题。 Maybe .NET has a generic producer-consumer-pattern already implemented? 也许.NET已经实现了通用的生产者 - 消费者模式? Maybe I can use Thread.GetData and Thread.SetData somehow? 也许我可以以某种方式使用Thread.GetData和Thread.SetData?

As an alternative to Ash's solution, consider the following example. 作为Ash解决方案的替代方案,请考虑以下示例。

Let's say you have two threads - one for receiving packets from a socket and another for processing those packets. 假设您有两个线程 - 一个用于接收来自套接字的数据包,另一个用于处理这些数据包。 Obviously, the Receiver thread needs to inform the Processor thread when a packet is available for processing, so the packets need to be shared between the threads somehow. 显然,当数据包可用于处理时,Receiver线程需要通知处理器线程,因此需要以某种方式在线程之间共享数据包。 I typically do this with a shared data queue. 我通常使用共享数据队列执行此操作。

At the same time, we don't necessarily want to tightly couple the threads together. 同时,我们不一定要将线程紧密耦合在一起。 For example, the Receiver thread shouldn't even know that a Processor thread exists. 例如,Receiver线程甚至不应该知道处理器线程存在。 All the Receiver needs to focus on is receiving packets from the network and then notifying any interested subscribers that packets are available for processing. 接收方需要关注的是从网络接收数据包,然后通知任何感兴趣的用户数据包可用于处理。 Events are the perfect way of achieving this in .NET. 事件是在.NET中实现这一目标的完美方式。

So here's some code. 所以这里有一些代码。

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();
}

I know there's a lot to digest there. 我知道那里要消化很多。 The program should work in .NET 3.5 providing you have UDP traffic on port 5006. 该程序应该在.NET 3.5中工作,只要您在端口5006上有UDP流量。

As far as data sharing between threads, the points of interest are the ProcessPackets() and OnPacketReceived() methods of the Processor class. 就线程之间的数据共享而言,兴趣点是Processor类的ProcessPackets()和OnPacketReceived()方法。 Notice that the OnPacketReceived() method occurs on the Receiver thread, even though the method is part of the Processor class, and that the queue is synchronized using a sync object. 请注意,OnPacketReceived()方法在Receiver线程上发生,即使该方法是Processor类的一部分,并且使用同步对象同步队列。

While it's not a built in solution you can create a class containing a private "sync" object. 虽然它不是内置解决方案,但您可以创建包含私有“同步”对象的类。 Then, in properties and method calls use the lock statement on the sync object to ensure serialized access. 然后,在属性和方法调用中,使用同步对象上的lock语句来确保序列化访问。

eg: 例如:

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;
           }

        }
    }
} 

Create an instance of DataClass() on one thread, then pass this instance to a second or more threads. 在一个线程上创建DataClass()的实例,然后将此实例传递给第二个或更多线程。 When needed access the thread safe Data property to pass/receive data between threads. 需要时访问线程安全的Data属性以在线程之间传递/接收数据。

看看这里 ,其中一些回答可能会回答你的问题。

As for Ash's solution: The problem with such "threadsafe" data-classes (I call them "pseudo-threadsafe"), especially if they have different members, is that these members might change in between threadsafe calls. 至于Ash的解决方案:这种“线程安全”数据类(我称之为“伪线程安全”)的问题,特别是如果它们有不同的成员,这些成员可能会在线程安全调用之间发生变化。 This applies for all multi-member classes, but is especially a problem in all enumerations (lists, arrays) as it makes the use of functions like ".Count" practically impossible (google for details). 这适用于所有多成员类,但在所有枚举(列表,数组)中尤其是一个问题,因为它使得像“.Count”这样的函数几乎不可能使用(google了解详情)。

Example: 例:

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];
       }
    }
}

Here someone might try to use something like this: 在这里有人可能会尝试使用这样的东西:

public void isJohn(pseudoThreadSafeHuman currentPerson) {
    if currentPerson.firstName == "John"  
       {
         MessageBox.Show(currentPerson.fullName)
       }
}

The members firstName, lastName and fullName are all threadsafe. 成员firstName,lastName和fullName都是线程安全的。 Still this might print something else than "John" as the value might change between the if and the MessageBox.Show(). 由于if和MessageBox.Show()之间的值可能会发生变化,因此这可能会打印除“John”之外的其他内容。 Another example: 另一个例子:

things like getInitials(pseudoThreadSafeHuman currentPerson) could throw exceptions: 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 ""
   }
}

This are really stupid examples of wrong code usage. 这是错误的代码使用的愚蠢的例子。 Additionally I don't know C# too well (am using VB.Net myself), so the syntax might be totally off. 另外我不太了解C#(我自己使用的是VB.Net),因此语法可能完全不合适。 Still I guess you get the idea. 我猜你还是明白了。 So in my opinion threadsafe-classes lead to programming errors, rather just use a classical synclock (which is also allot more readable for other programmers). 因此在我看来,线程安全类会导致编程错误,而只是使用经典的synclock(对其他程序员来说也更具可读性)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM