【问题标题】:Correct way to avoid a ObjectDisposedException, in this case?在这种情况下,避免 ObjectDisposedException 的正确方法是什么?
【发布时间】:2018-01-23 12:40:55
【问题描述】:

我目前在下一行得到一个 ObjectDisposedException。

var client = ((Socket) asyncResult.AsyncState).EndAccept(asyncResult);

System.ObjectDisposedException: '无法访问已处置的对象。 对象名称:'System.Net.Sockets.Socket'。'

我只是想知道,避免此类错误的正确方法(就最佳实践而言)是什么?我不确定如何处理它,如何检查它是否事先处理,但这是我应该做的吗?或检查其他内容。

我是自学 C#,所以我从来没有学过这样的东西,有人可以提供一些见解吗?

这是完整的课程:

internal sealed class SocketHandler : IDisposable
{
    private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();

    private readonly Socket _serverSocket;

    public SocketHandler()
    {
        _serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        Load();
    }

    public void Dispose()
    {
        _serverSocket?.Close(); // close also calls dispose...
    }

    private void Load()
    {
        var config = Program.Server.ConfigHandler;

        _serverSocket.Bind(new IPEndPoint(IPAddress.Any, config.GetConfigValueByKey("network.sockets.port").ToInt()));
        _serverSocket.Listen(int.Parse(config.GetConfigValueByKey("network.sockets.backlog")));
        _serverSocket.BeginAccept(OnAcceptConnection, _serverSocket);
    }

    private void OnAcceptConnection(IAsyncResult asyncResult)
    {
        try
        {
            if (_serverSocket == null)
            {
                return;
            }

            var client = ((Socket) asyncResult.AsyncState).EndAccept(asyncResult);

            var playerHandler = Program.Server.BaseHandler.PlayerHandler;
            var players = playerHandler.Players;

            var config = Program.Server.ConfigHandler;

            var maxConnections = int.Parse(config.GetConfigValueByKey("game.players.limit"));
            var maxConnectionsPerIp = int.Parse(config.GetConfigValueByKey("game.players.ip_limit"));

            if (players.Count >= maxConnections)
            {
                Logger.Warn("Incoming connection was refused because the player limit was exceeded.");

                client.Shutdown(SocketShutdown.Both);
                client.Close();

                return;
            }

            if (players.Values.Count(x => x != null && !x._disconnected && x.getIp() == client.RemoteEndPoint.ToString().Split(':')[0]) > maxConnectionsPerIp)
            {
                Logger.Warn("Incoming connection was refused because the IP limit was exceeded.");

                client.Shutdown(SocketShutdown.Both);
                client.Close();

                return;
            }

            var clientId = Randomizer.Next(1, 10000);

            Program.Server.BaseHandler.PlayerHandler.TryAddPlayer(clientId, new Player(clientId, client, new InitialPacketParser()));
        }
        catch (SocketException socketException)
        {
            Logger.Fatal(socketException, "Failed to accept socket connection.");
        }
        finally
        {
            _serverSocket?.BeginAccept(OnAcceptConnection, _serverSocket);
        }
    }
}

【问题讨论】:

  • 由于套接字是private,我猜(虽然我不是 100% 确定)您的代码是唯一处理/关闭此套接字的代码。因此,您可以自己跟踪该状态(可能需要以线程安全的方式执行此操作)。
  • 有没有这样的机会。从外面调用你的班级?您还应该在 dispose 中将 _serversocket 设置为 null 以使您检查 Eventhandler 工作

标签: c# .net error-handling dispose


【解决方案1】:

据我了解,reference sourceSocket 本身并不是 Dispose。因此,由于您的_serverSocketprivate,因此您是唯一可以控制何时处理它的人。

您的 OnAcceptConnection() 方法已经开始尝试检查,但不是完全。

在您的Dispose() 方法中(或您Close()Dispose() 您的_serverSocket 的任何其他地方)您还需要将_serverSocket 设置为null。您可以以线程安全的方式执行此操作:

public class SocketHandler
{
    private Socket _serverSocket; // not read-only

    /* ... */

    public void Dispose()
    {
        Socket tmp = _serverSocket; // save instance
        _serverSocket = null; // set field to null
        tmp?.Close();
    }

现在您的OnAcceptConnection() 在检查if (_serverSocket == null) 时返回,您可以避免异常。

【讨论】:

    【解决方案2】:

    问题也可能来自于您最初是如何使用 SocketHandler 的。 我认为 IDisposable 模式在您的类中的实现方式没有任何问题。避免在 Disposable 类被释放后访问它的常规方法是将其包装在 using 语句中,该语句将在离开块后自动释放该类:

    using( SocketHandler handler = new SocketHandler())
    {
        (...)
    } //handler will be disposed and not accessible after here
    

    有关此 Microsoft Docs 的详细信息,请联系article explaining IDispose

    编辑: 感谢您指出我理解问题的方式完全错误。

    据我所知,我至少重新创建了场景:

    class Program
    {
        static void Main(string[] args)
        {
            using (C1 instance = new C1())
            {
                Task.Factory.StartNew(() =>
                {
                    Task.Delay(1000);
    
                    bool disposed = (bool)typeof(C1).GetField("disposed", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(instance);
    
                    if (disposed)
                    {
                        Console.WriteLine("Already disposed will not call DoSomething()");
                    }
                    else
                    {
                        instance.DoSomething();
                    }
    
                });
            }
    
            Console.ReadKey(true);
        }
    }
    
    class C1 : IDisposable
    {
        bool disposed = false;
        public C1()
        {
    
        }
    
        public void DoSomething()
        {
            if (disposed)
                throw new ObjectDisposedException("C1");
    
            Console.WriteLine("Still existing!");
        }
    
    
        public void Dispose()
        {
            Dispose(true);
    
            Console.WriteLine("Disposed!");
        }
    
        protected virtual void Dispose(bool disposing)
        {
            if (disposed)
                return;
            disposed = true;
        }
    }
    

    并设法通过反射解决异常。假设微软将根据其框架设计指南使用相同的模式+命名,这个解决方案至少可以用作解决方法

    但我非常怀疑这是最好的方法。

    【讨论】:

    • OP的问题是OnAcceptConnection是从另一个线程调用或者异步调用的。这仍然可能在套接字甚至它们的SocketHandler 实例被释放后很长时间内发生。问题是在调用OnAcceptConnection 时如何确定_serverSocket(或asyncResult.AsyncState 中的套接字)是否已经被释放。您的 using 声明对此无济于事。
    猜你喜欢
    • 2011-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多