簡體   English   中英

如何在線程之間傳遞數據?

[英]How to pass data between threads?

在.NET中的線程之間傳遞數據的方法是什么? 我目前可以想到兩件事:

  1. 成員變量,例如使用producer-consumer-queue模式。
  2. 在啟動線程時使用ParameterizedThreadStart委托。 (僅適用於一次,不適合長時間運行的后台工作線程)。

.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM