如何在来自相同 class 和其他 class 的不同对象之间传递信息作为 C# 中的事件

How to pass information between different objects from the same class and other classes as events in C#

我画了下面的拓扑结构,其中每个节点都是classSensorNode的一个对象,蓝色的链接表示每个节点与它的邻居之间的链接,节点周围的圆圈表示传输范围对于每个节点。 接收器也是 class Sink 的对象。 我需要实例化它们之间的消息传递和通信,但我不知道应该使用什么机制在这些对象(传感器节点)之间执行消息传递,其中每个节点都有其唯一的 ID,接收器有一个固定的 ID,在我的代码中为 1因为我只用一个水槽。

以下是class我仍然坚持如何实现发送接收和转发以使这种通信适用于这些不同对象之间的问题...

Class“传感器节点”

namespace CRN_Topology
{
    class SensorNode
    {
       public int snID;
       public string snName;
       public int snDepth;
       public DateTime schedulingTime;
       public double holdingTime;
       public double energy;
       
       public List<int> queue11 = new List<int>();
       public List<DateTime> queue12 = new List<DateTime>();

       public List<Packet> queue21 = new List<Packet>();
       public List<DateTime> queue22 = new List<DateTime>();

       public SensorNode(int id,string name,int depth, double energy)
       {
           this.snID = id;
           this.snName = name;
           this.snDepth = depth;
           this.energy = energy;
       }

       public void insertHistoryQueue(int packetID, DateTime receivingTime)
       {
           queue11.Add(packetID);
           queue12.Add(receivingTime);
       }

       public void insertPriorityQueue(Packet packet, DateTime schedulingTime)
       {
           queue21.Add(packet);
           queue22.Add(schedulingTime);
       }

       public DateTime schedulingTimeCalculations(double holdingTime, DateTime systemTime)       
       {
           schedulingTime = DateTime.FromOADate(holdingTime).Date + systemTime.TimeOfDay;

           return schedulingTime; 
       }

       public double holdingTimeCalculations(double alpha, double depth, double beta)
       {
           holdingTime = alpha * depth + beta;

           return holdingTime; 
       }

       public void receive(Packet packet)
       {
       }

       public void forward(Packet packet, int neighborID)
       {
       }

       public void remove()
       {
       }

       public void sendDirect(int rxID, Packet packet)
       {
       }
    }
}

Class“下沉”

namespace CRN_Topology
{
    class Sink
    {
        public string name;
        public int sinkID;
        public int sinkX;
        public int sinkY;

        public List<Packet> queue1 = new List<Packet>();
        public List<DateTime> queue2 = new List<DateTime>();
 
        public Sink(string name, int Id , int xLocation, int yLocation)
        {
            this.name = name;
            this.sinkID = Id;
            this.sinkX = xLocation;
            this.sinkY = yLocation;
        }

        public void insert(Packet packet, DateTime receivingTime)
        {
            queue1.Add(packet);
            queue2.Add(receivingTime);
        }
    }
}

任何想法,我需要你的建议和帮助,因为我不知道如何在这些对象(传感器节点)之间以及传感器节点和接收器之间传递信息。在 C# 中负责此应用程序的库是什么?

您可以使用聚合关系来实现您所需要的。假设任何接收器只能连接两个节点,每个接收器 class 必须包含两个类型 SensorNode 的属性。例如:

public class Sink
{
     public SensorNode Node1 { get; set; }
     public SensorNode Node1 { get; set; }
     //...
}

这允许您控制节点之间的关系,因此您可以访问通过接收器连接的每个节点。在此对象上调用方法允许您启动对象之间的交互。顺便说一句,SensorNode class 还可以包含对其所有接收器列表的引用,以便通过它自己的方法与它们进行交互:

public class SensorNode
{
     public List<Sink> ConnectedSinks { get; set; }
}

PS:在面向对象语言中使用public字段不是一个很好的主意,所以你最好考虑使用public 属性代替。

您可以使用实际 events. Yet, for this case IObservable and IObserver 似乎可以提供更好的模式。虽然在尝试实现这一点时,我很快就摆脱了这种模式。

以下是我开发的解决方案。我展示的是一个摘要 class Node,旨在用作 SensorNodeSink 的基础,因为两者都可以接收连接。

编辑 1:或者你可以把它变成自己的东西并使用组合,你可以实现抽象方法 Recieve 来引发自定义事件。

编辑 2:也许 Recieve 方法的东西比 Send 更好?我的意思是,在我的代码中,预期的实现是让它使用 _connections 进行广播或尝试将 Packet 到达其目的地,并进行日志记录和其他任何操作。 我真的不知道这是否是您为 Recieve 方法设计的。

abstract class Node
{
    /// <summary>
    /// Set of all the ids.
    /// </summary>
    private static readonly Dictionary<int, object> _nodes;

    /// <summary>
    /// The Id of the node.
    /// </summary>
    /// <remarks>Can't change.</remarks>
    private readonly int _id;

    /// <summary>
    /// The connections of the node.
    /// </summary>
    protected readonly Dictionary<int, Node> _connections;

    static Node()
    {
        _nodes = new Dictionary<int, object>();
    }

    protected Node(int id)
    {
        // Try register the Id provided
        if (_nodes.ContainsKey(id))
        {
            // If we fail to add it, it means another Node has the same Id already.
            throw new ArgumentException($"The id {id} is already in use", nameof(id));
        }
        _nodes.Add(id, null);
        // Store the Id for future reference
        _id = id;
        _connections = new Dictionary<int, Node>();
    }

    ~Node()
    {
        // Try to release the Id
        // AppDomain unload could be happening
        // Any reference could have been set to null
        // Do not start async operations
        // Do not throw exceptions
        // You may, if you so desire, make Node IDisposable, and dispose including this code
        var nodes = _nodes;
        if (nodes != null)
        {
            nodes.Remove(Id);
        }
    }

    /// <summary>
    /// The Id of the Node
    /// </summary>
    public int Id { get => _id; }

    /// <summary>
    /// Connects nodes, bidirectionally.
    /// Connect(x, y) is equivalent to Connect(y, x).
    /// </summary>
    /// <param name="x">The first node to connect</param>
    /// <param name="y">The second node to connect</param>
    public static void Connect(Node x, Node y)
    {
        if (x == null)
        {
            throw new ArgumentNullException(nameof(x));
        }
        if (y == null)
        {
            throw new ArgumentNullException(nameof(y));
        }
        // Bidirectional
        x._connections[y.Id] = y;
        y._connections[x.Id] = x;
    }

    /// <summary>
    /// Disconnects nodes, bidirectionally.
    /// Disconnect(x, y) is equivalent to Disconnect(y, x).
    /// </summary>
    /// <param name="x">The first node to connect</param>
    /// <param name="y">The second node to connect</param>
    public static void Disconnect(Node x, Node y)
    {
        if (x == null)
        {
            throw new ArgumentNullException(nameof(x));
        }
        if (y == null)
        {
            throw new ArgumentNullException(nameof(y));
        }
        // Short circuit
        if (y._connections.ContainsKey(x.Id) && x._connections.ContainsKey(y.Id))
        {
            // Bidirectional
            x._connections.Remove(y.Id);
            y._connections.Remove(x.Id);
        }
    }

    protected abstract void Recieve(Packet value);
}

注意:我没有添加任何东西来阻止 Node 到自身的连接

我已经留下Recieve摘要给你实现。 Sink 可能只会记录消息,而 SensorNode 将必须检查目的地并转发消息。

要从一个节点向另一个节点发送消息,请使用字段 _connections. The key is theidof the connectedNode. Thefore, if you want to broadcast, you can iterate over_connections`。如果你想 运行 它们并行,我在下面有一个线程安全的版本。

我考虑到您可能需要为连接附加信息(例如重量/距离/成本/延迟/延迟)。如果是这种情况,请考虑创建一个 Connection class 并使 _connections 成为它的字典。它的实用优势是您可以将相同的 Connection 对象添加到两个 Nodes ,然后对它们的更新将对它们可见。 或者只是使用Tuple或者添加更多词典,随便什么,我不在乎。


我花了一段时间才想出一个好的线程安全实现。如果它使用 Monitor ,它会阻止读取连接字典,你需要这样做才能发送 Pakets,所以这不好。读写锁要好一些,但它可能会导致 ConnectDisconnect 方法耗尽。

我想出的是一个很好的旧状态机。我添加了另一本字典来保持状态。使所有字典 ConcurrentDictionary 允许并行操作并能够自动修改状态。

代码如下:

abstract class Node
{
    /// <summary>
    /// Set of all the ids.
    /// </summary>
    private static readonly ConcurrentDictionary<int, object> _nodes;

    /// <summary>
    /// The Id of the node.
    /// </summary>
    /// <remarks>Can't change.</remarks>
    private readonly int _id;

    /// <summary>
    /// The connections of the node.
    /// </summary>
    protected readonly ConcurrentDictionary<int, Node> _connections;

    /// <summary>
    /// Status of the connection for synchronization
    /// </summary>
    private readonly ConcurrentDictionary<int, int> _connectionStatus;

    private const int _connecting = 0;
    private const int _connected = _connecting + 1;
    private const int _disconnecting = _connected + 1;

    static Node()
    {
        _nodes = new ConcurrentDictionary<int, object>();
    }

    protected Node(int id)
    {
        // Try register the Id provided
        if (!_nodes.TryAdd(id, null))
        {
            // If we fail to add it, it means another Node has the same Id already.
            throw new ArgumentException($"The id {id} is already in use", nameof(id));
        }
        // Store the Id for future reference
        _id = id;
        _connections = new ConcurrentDictionary<int, Node>();
        _connectionStatus = new oncurrentDictionary<int, int>();
    }

    ~Node()
    {
        // Try to release the Id
        // AppDomain unload could be happening
        // Any reference could have been set to null
        // Do not start async operations
        // Do not throw exceptions
        // You may, if you so desire, make Node IDisposable, and dispose including this code
        var nodes = _nodes;
        if (nodes != null)
        {
            nodes.TryRemove(Id, out object waste);
        }
    }

    /// <summary>
    /// The Id of the Node
    /// </summary>
    public int Id { get => _id; }

    /// <summary>
    /// Connects nodes, bidirectionally.
    /// Connect(x, y) is equivalent to Connect(y, x).
    /// </summary>
    /// <param name="x">The first node to connect</param>
    /// <param name="y">The second node to connect</param>
    public static bool Connect(Node x, Node y)
    {
        if (x == null)
        {
            throw new ArgumentNullException(nameof(x));
        }
        if (y == null)
        {
            throw new ArgumentNullException(nameof(y));
        }
        // Bidirectional
        // Take nodes in order of Id, for syncrhonization
        var a = x;
        var b = y;
        if (b.Id < a.Id)
        {
            a = y;
            b = x;
        }
        if (a._connectionStatus.TryAdd(b.Id, _connecting)
            && b._connectionStatus.TryAdd(a.Id, _connecting))
        {
            a._connections[b.Id] = b;
            b._connections[a.Id] = a;
            a._connectionStatus[b.Id] = _connected;
            b._connectionStatus[a.Id] = _connected;
            return true;
        }
        return false;
    }

    /// <summary>
    /// Disconnects nodes, bidirectionally.
    /// Disconnect(x, y) is equivalent to Disconnect(y, x).
    /// </summary>
    /// <param name="x">The first node to connect</param>
    /// <param name="y">The second node to connect</param>
    public static bool Disconnect(Node x, Node y)
    {
        if (x == null)
        {
            throw new ArgumentNullException(nameof(x));
        }
        if (y == null)
        {
            throw new ArgumentNullException(nameof(y));
        }
        // Short circuit
        if (!y._connections.ContainsKey(x.Id) && !x._connections.ContainsKey(y.Id))
        {
            return false;
        }
        // Take nodes in order of Id, for syncrhonization
        var a = x;
        var b = y;
        if (b.Id < a.Id)
        {
            a = y;
            b = x;
        }
        if (a._connectionStatus.TryUpdate(b.Id, _disconnecting, _connected)
            && b._connectionStatus.TryUpdate(a.Id, _disconnecting, _connected))
        {
            a._connections.TryRemove(b.Id, out x);
            b._connections.TryRemove(a.Id, out y);
            int waste;
            a._connectionStatus.TryRemove(b.Id, out waste);
            b._connectionStatus.TryRemove(a.Id, out waste);
            return true;
        }
        return false;
    }

    protected abstract void Recieve(Packet value);
}

线程喋喋不休:

ConnectDisconnect一定要尽量对同一个订单进行操作,这就是为什么我用Id下订单的原因。 ConnectDisconnect 并发执行可能会导致单向连接。

如果您的线程正在尝试添加相同的连接,则只有一个会成功(由于 TryAdd)。如果两个线程试图删除同一个连接,则只有一个会成功(由于 TryUpdate)。如果连接存在,Connect 将失败。如果连接不存在 Disconnect 将失败。

如果 ConnectDisconnect 同时发生并且连接存在,Connect 将无法添加它并失败,除非 Disconnect 设法先删除它。如果连接不存在,Disconnect 将失败,除非 Connect 设法首先添加它。

状态_connecting_disconnecting是为了防止ConnectDisconnect看到connectin在一个方向存在而在另一个方向不存在的情况。

没有线程需要等待另一个线程完成。并且只读取 _connections.

时不需要同步

虽然理论上只读取 _connections 的线程可能能够看到连接仅存在于一个方向的情况,但该线程将无法发送 Packet同时两个方向。因此,从该线程的角度来看,在其尝试发送 Packet.

之间添加或删除了连接

没有验证连接是否存在的方法,这种方法不可靠,因为一旦线程检查连接是否存在,另一个线程可能会在第一个线程能够使用连接之前删除连接。防止连接被删除不是要求的一部分,但可以将其添加为另一种连接状态。