我如何知道 UdpClient 是不是已关闭/处置?

Posted

技术标签:

【中文标题】我如何知道 UdpClient 是不是已关闭/处置?【英文标题】:How do I know if UdpClient has been closed/disposed?我如何知道 UdpClient 是否已关闭/处置? 【发布时间】:2012-03-07 19:12:15 【问题描述】:

我通过通常的异步回调从 UdpClient 接收数据:

private void OnUdpData(IAsyncResult result)

    byte[] data = _udpReceive.EndReceive(result, ref _receiveEndPoint);

    //Snip doing stuff with data

    _udpReceive.BeginReceive(OnUdpData, null);

当我在主线程中Close() UdpClient 时,回调会按我的预期触发,但此时_udpReceive 已经被释放,当我尝试调用EndReceive() 时我得到一个ObjectDisposedException。我原以为只会得到一个空缓冲区。

处理这个问题的正确方法是什么? UdpClient 的某些成员在尝试使用它之前我可以检查,还是将其全部包装在 try 中并捕获 ObjectDisposedException 的唯一方法?对于正常的收盘来说,这似乎很糟糕。

【问题讨论】:

【参考方案1】:

您可以执行此操作以检查其是否已处理。处理 UdpClient 时,Client 设置为 null。

private void OnUdpData(IAsyncResult result)

    if (_udpReceive.Client == null)
        return;
    byte[] data = _udpReceive.EndReceive(result, ref _receiveEndPoint);

    //Snip doing stuff with data

    if (_udpReceive.Client == null)
        return;
    _udpReceive.BeginReceive(OnUdpData, null);

虽然因为您是在单独的线程中关闭它,但您最终可能会遇到竞争条件。最好只捕获 ObjectDisposedException 和 SocketException。

private void OnUdpData(IAsyncResult result)

    try
    
        byte[] data = _udpReceive.EndReceive(result, ref _receiveEndPoint);

        //Snip doing stuff with data

        _udpReceive.BeginReceive(OnUdpData, null);
    
    catch (Exception e)
    
        //You may also get a SocketException if you close it in a separate thread.
        if (e is ObjectDisposedException || e is SocketException)
        
            //Log it as a trace here
            return;
        
        //Wasn't an exception we were looking for so rethrow it.
        throw;
    

【讨论】:

回调是否解除了调用EndReceive的责任之一?我的印象是,除了不占用资源的某些类型的IAsyncResult(例如Control.BeginInvoke 的返回值)之外,应该在每个BeginXX 之后总是调用EndXX。请注意,捕获的异常并不是EndReceive 操作失败的标志,而是“成功”的EndReceive 调用中继导致接收尝试的异常的结果。 EndX 不只是为了从 BeginX 获取数据吗?好吧,在这种情况下,无论哪种方式,回报都很好。 EndReceive 将检查同样的事情并抛出一个已处理的异常。【参考方案2】:

这完全是设计使然。你做了一些特殊的事情,你关闭了套接字,即使你期望接收到数据。所以你会得到一个例外。 .NET 框架始终确保异步调用已完成,并且当您调用 EndXxx() 时,会在回调中发出中止原因的信号。好主意,它可以让您清理与回调关联的任何状态。

您可以通过等到传输完成,停止调用 BeginReceive() 并然后关闭套接字来使其无异常。但这并不总是可行的,或者有时您真的想提前终止。没问题,只需捕获 ObjectDisposedException 并退出即可。当然,确实考虑一下线路另一端的应用程序会发生什么。它之后发送的任何内容都将落入比特桶中,无法发现。

【讨论】:

【参考方案3】:

根据您的问题,听起来您希望避免在强制客户端关闭时引发异常。我将对您的代码进行一些猜测并尝试提供解决方案:

由于您有一个名为“OnUdpData”的方法,我假设您有一个围绕 UDPClient 的包装类。在该包装类中,您可以执行以下操作:设置一个标志,指示您正在关闭客户端,并且在尝试关闭客户端之前 不应再立即使用该客户端。这避免了在调用EndReceive() 之前检查_udpReceive.Client == null 导致的竞争条件,因为主线程可以在客户端条件检查之后关闭客户端。

    private bool _finishedListening = false;

    public void StopListener()
    
        _finishedListening = true;
        _udpReceive.Close();
    

    private void OnUdpData(IAsyncResult result)
    
      if (_finishedListening == true)
        return;
      byte[] data = _udpReceive.EndReceive(result, ref _receiveEndPoint);
      //Snip doing stuff with data
      _udpReceive.BeginReceive(OnUdpData, null);
    

【讨论】:

以上是关于我如何知道 UdpClient 是不是已关闭/处置?的主要内容,如果未能解决你的问题,请参考以下文章

是否应该处置 UdpClient?

重用 UdpClient 与处置它

无法访问已处置的对象 - 如何修复?

检查物品是不是已被处置的正确方法

我如何知道一个 mgo 会话是不是已关闭

WKWebView 正在尝试在后台运行,尽管它已关闭并且应该被处置