首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >System.Net.Sockets的发送/接收包装器-后续

System.Net.Sockets的发送/接收包装器-后续
EN

Code Review用户
提问于 2019-06-27 18:25:54
回答 1查看 101关注 0票数 4

最近,我在NetworkEndpoint类中发布了一个关于提高线程和套接字安全性的问题。以前的职位

我已经实施了在我得到的答案中所建议的修改。当我要求第二次检查以确认我的变化时,我被告知发布另一个问题。

下面是我修改的代码以及一些后续问题。要查看全班同学,请参考我之前的帖子。

  1. 我在已连接属性中使用锁以及发送和断开连接方法有效吗?
代码语言:javascript
复制
private object connectedLock = new object();
public bool Connected {
    get {
        lock (connectedLock) {
           if (connection == null) return false;
           return connection.Connected;
        }
    }
}

private object sendLock = new object();
public void Send(NetworkHeader header, byte[] data) {
    if (!Connected) throw new InvalidOperationException("NetworkEndpoint must be connected before sending.");
    try {
        lock (sendLock) {
            connection.Send(ByteUtils.Combine(header.Serialize(), data));
        }

    } catch (SocketException) {
        Disconnect();
    }
}

private object disconnectLock = new object();
public void Disconnect() {
   if (Connected) {
        lock (disconnectLock) {
            connection?.Shutdown(SocketShutdown.Both);
            connection?.Close();
            connection = null;
            OnDisconnected();
            Clear();
       }
    }
}
  1. 我添加到InitializeReceiveLoop中的锁是否由于if (Receiving) return;而多余?
代码语言:javascript
复制
private object initializeLock = new object();
public void InitializeReceiveLoop() {
    lock (initializeLock) {
        if (Receiving) return;
        Receiving = true;
    }
    BeginReceive();
}
  1. 这是澄清事件的最好办法吗?将事件设置为null是否可以实现相同的目标?
代码语言:javascript
复制
public void Clear() {
    foreach (Delegate d in DataReceived.GetInvocationList())
        DataReceived -= (EventHandler<NetworkReceiveEventArgs>)d;
    foreach (Delegate d in Disconnected.GetInvocationList())
        Disconnected -= (EventHandler)d;
}
  1. 这个EndReceive的实现比我以前的要好吗?
  2. EndReceive的使用方式不是已经“线程安全”了吗?它是一个私有方法,仅由BeginReceive调用,只在上次调用EndReceive之后调用,或者由InitializeReceiveLoop调用,后者只能调用一次(如果接收已经为真,则立即返回)。
代码语言:javascript
复制
private void EndReceive(IAsyncResult result) {
    byte[] dataBuffer = null;
    NetworkHeader header = null;

    try {
        if (connection.EndReceive(result) > 0) {
            header = NetworkHeader.Deserialize(headBuffer);
            dataBuffer = new byte[header.DataSize];

            int offset = 0;
            while (offset < header.DataSize) {
                int lengthRead = connection.Receive(dataBuffer, offset,
                    (int)header.DataSize - offset, SocketFlags.None);

                if (lengthRead == 0) {
                    Disconnect();
                    return;
                }

                else offset += lengthRead;
            }
        }
    } catch (SocketException) {
        Disconnect();
        return;
    }

    OnDataReceived(header, dataBuffer);
    BeginReceive();
}
EN

回答 1

Code Review用户

发布于 2019-06-27 20:34:44

可以更简单地清除事件:

公开无效Clear() {DataReceived.GetInvocationList()中代表d) DataReceived -= (EventHandler)d;

代码语言:javascript
复制
public void Clear() {
    DataReceived = null;
    Disconnected = null;
}

这绝不是对代码的全面回顾,但我想向您解释几件事。

您正在使用单独的锁发送、检查连接状态、初始化和断开连接。请考虑使用单独的锁对所有需要公共线程感知的资源Connected的操作所产生的影响。因为它们都采用不同的锁,所以它们可以同时更改状态并破坏另一个正在进行的操作。这就是为什么所有这些操作都应该使用单一锁的原因。

代码语言:javascript
复制
private object syncRoot = new object();

但即便如此,由于您在锁定之前检查了条件,数据完整性仍可能被不幸的事件流破坏。

代码语言:javascript
复制
public void Send(NetworkHeader header, byte[] data) {
    if (!Connected) // ..      // <- outside lock
    lock (syncRoot) {          // <- inside lock, but condition might no longer be true
        // ..
    }
}

例如,假设两个线程同时调用SendDisconnect。一种可能的流动是:

代码语言:javascript
复制
- thread A: call Send                    // thread A is first
- thread A: check IsConnected: true
- thread B: call Disconnect              // thread B starts a fraction later
- thread B: check IsConnected: true
- thread B: take lock
- thread B: set IsConnected: false
- thread B: release lock
- thread A: take lock                    // thread A reaches a point where it 
             // expected its state to still be valid, but thread B changed it
- thread A: connection.Send(..)          // <- null-reference exception

以前有一次关于双重检查锁的建议。但是这里有一个很好的理由不去使用它。所以我们得把锁里的条件取下来。

代码语言:javascript
复制
public void Send(NetworkHeader header, byte[] data) {
    lock (syncRoot) { 
        if (!Connected) // ..   
        // ..
    }
}

接下来,我们可以将Connected更改为接受易失布尔,因此在获取其值时不需要额外的锁定。只需确保在建立连接并在Disconnect上重置时设置它的值。易失性字段被读取为原子操作。您得到的是实际值,而不是缓存值(这可能是为了在非并发上下文中优化状态)。

代码语言:javascript
复制
private volatile bool _connected = false;

public bool Connected => _connected;   // <- no locking required

我希望这能解释一些锁和线程安全的基本概念。

票数 1
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/223087

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档