Отключение клиентов от сервера: Доступ к ликвидированному объекту невозможен

Запуск сервера:

                await Task.Run(() =>
                {
                    while (active)
                    {
                        try
                        {
                            Socket client = server.Accept();
                            LogEvent($"{client.RemoteEndPoint} is connected");

                            Task.Run(() =>
                            {
                                if (ProcessClient(client)) //тут добавление в `clientList` и обработка
                                {
                                    DisconnectClient(client);
                                }
                            });
                        }
                        catch (Exception ex)
                        {
                            System.Diagnostics.Debug.WriteLine(ex.Message);
                            active = false;
                        }
                    }
                });

Код отключения клиентов:

        private void DisconnectClient(Socket client, bool autoRemove = true)
        {
            try
            {
                LogEvent($"{client.RemoteEndPoint} is disconnected");
                client.Shutdown(SocketShutdown.Both);
                if (client.Connected)
                {
                    client.Disconnect(false);
                }
                if (autoRemove)
                {
                    clientList.Remove(client);
                }
                client.Close();
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }
        }

        private void DisconnectAllClients()
        {
            if (clientList != null)
            {
                clientList.ForEach((client) =>
                {
                    DisconnectClient(client, false);
                });
                clientList.Clear();
            }
        }

Когда при нажатии кнопки вызываю DisconnectAllClients(), то здесь:

if (ProcessClient(client))
{
    DisconnectClient(client); <<здесь
}

возникает экскепшен

Вызвано исключение: "System.ObjectDisposedException" в System.dll
Доступ к ликвидированному объекту невозможен.
Имя объекта: "System.Net.Sockets.Socket".

По-этому, приходится делать через try. Не пойму, нормальное ли это поведение и можно ли этого избежать ни во что не оборачивая Socket? :thinking:

Может быть надо вызывать только что-то одно из Shutdown и Disconnect?

c# - What exactly do socket's Shutdown, Disconnect, Close and Dispose do? - Stack Overflow

в доках написано To ensure that all data is sent and received before the socket is closed, you should call Shutdown before calling the Disconnect method. :man_shrugging:

А исключение-то где именно выдает?
Там же должен быть стектрейс со всеми номерами строк.

На строчке LogEvent($"{client.RemoteEndPoint} is disconnected");. Если не делать Close, то всё норм. Ещё пробовал занулять клиент, но он не зануляется, почему-то :thinking:

Так тут похоже просто DisconnectClient почему-то вызывается больше одного раза для одного и того же объекта.

Так это понятно. В этом и проблема. Сначала метод DisconnectAllClients отключает и убивает всех клиентов. А потом ещё раз здесь:

if (ProcessClient(client))
{
    DisconnectClient(client); <<здесь
}

Вопрос - как этого избежать без дополнительных обёрток, и можно ли вообще?

Удалять из clientList после отключения если DisconnectAllClients в конце.
А если наоборот, то сложнее. Можно тоже проверять этот список например.

Оно же и так удаляется. Но это не помогает, потому что здесь

if (ProcessClient(client))
{
    DisconnectClient(client);
}

прямая ссылка на объект, а не перебор списка.

Но список находится в главном потоке. Может случиться сика-вава.

Тут и так может случиться, поток же как минимум добавляет туда. Да и сам объект сокета.
Надо синхронизировать действия с ними. Еще Concurrent коллекции могут помочь. Но не везде, например, не для случая когда один поток начал отключение, но еще не удалил, а второй тоже отключает. Тут надо как-то синхронизировать эти операции полностью, или может быть хотя бы удалять в начале.

Добавляется и удаляется из списка и так в главном потоке. А тут:

                if (autoRemove)
                {
                    clientList.Remove(client);
                }

Я просто забыл вызвать синхронизацию, когда вчера удалил и заново добавил этот блок.

То есть, Connect Disconnect и т.д. у одного объекта вызывать только в том потоке, в котором он был создан?

Не обязательно в том же потоке, но надо синхронизировать чтоб разные потоки не делали с ним что-то одновременно. Способы разные есть, например, Monitor + lock.

то есть, если несколько потоков попытаются сделать

lock (client)
{
...
}

то будут ждать друг друга и по очереди выполнят этот блок?

Да.

Когда поток формы начинает убивать сокеты, он делает это во время передачи данных через этот сокет в другом потоке. Почему тут сика-вава не случается? :thinking: