Всем доброго дня!
На лабораторной (да я студент) задали реализовать чат. В ходе реализации решил использовать асинхроный метод приема данных от клиента, и путём вызова событий обрабатыват их (например при приходе текстового сообщения от клиента вызывается событие getTextMs). Также на серверной части хранится список всех подключёных пользователей (для рассылки полученых сообщений).
При любом обращении к списку пользователей я использую lock чтобы не вылететь из перечисления. Например когда пользователь выходит из сети, то вызывается функция которая удаляет его из списка, и если другой поток в это время рассылал данный - выходит не очень красиво.
Собственно проблема - указаный элемент в lock не блокируется. И когда я запускаю к примеру сразу десять окон и одновременно их отключаю - сервер вылетает с InvalidOperationException Collection was modified; enumeration operation may not execute. (то есть у меня во время перечисления this.clientList изменился).
Код C#
private readonly Object locker = new Object();
...
private void client_getExit(object sender, ExitEventArgs e)
{
ServerClient client = sender as ServerClient;
lock (this.locker)
{
this.clientList.Remove(client);
if (client.IsAuthorization)
{
//рассылка всем пользователям, что клиент вышел из сети.
OutServerMessage serverMessage = new OutServerMessage(client.Id);
foreach (ServerClient serverClient in this.clientList)
{
serverClient.SendMessage(serverMessage);
}
}
}
}
this.clientList - список всех подключенных пользователей.
serverMessage - сообщение, которое отправляется на клиент.
serverClient.SendMessage - отправление текущего сообщения пользователю.
Я сначала думал, что всё это каким-то чудом висит в одном потоке, но проверка по System.Threading.Thread.CurrentThread.ManagedThreadId показала разные числа (надеюсь то смотрел).
также решил ради "шутки" добавить ещё одну переменную и посмотреть в дэбаге, при ошибке
Код C#
private readonly Object locker = new Object();
int kso = 0;
...
private void client_getExit(object sender, ExitEventArgs e)
{
ServerClient client = sender as ServerClient;
lock (this.locker)
{
this.kso++;
this.clientList.Remove(client);
if (client.IsAuthorization)
{
//рассылка всем пользователям, что клиент вышел из сети.
OutServerMessage serverMessage = new OutServerMessage(client.Id);
foreach (ServerClient serverClient in this.clientList)
{
serverClient.SendMessage(serverMessage);
}
}
this.kso--;
}
}
так вот... переменная this.kso при вылавливании ошибки показывала от 2 до 5.
P.S. Пожалуйста, отзовитесь люди! Я уже второй день с этой ошибкой воюю... ='(
Примечание:
-- как сервер обрабатывает запросы клиента?
Имеется отдельный класс DataChanel отвечающий за прием сообщения, в нашем случаем получение объекта класса Message, в котором описаны нужные нам поля, (скажем текст сообщения и от кого оно пришло). В этом классе находится сокет, который асинхронно считывает входящие данный, и когда сообщение полностью получено, генерируется событие getMessage, одним из параметров которого - наше полученное сообщение.
-- сколько потоков на сервере занимаются обработкой пула запросов?
Как таковые, потоки не выделяют. Сервер просто обрабатывает событие getMessage и рассылает всем/указаным пользователям сообщение. То есть подписчик работает с общим ресурсом "список пользователей, что онлайн"
-- как эти потоки взаимодействуют друг с другом?
Никак. Главное чтобы пока у одного подписчика занят список, второй его не трогал и ждал.
-- какое отношение имеет лок потока ThreadId=1 к локу потока ThreadId=2?
Хоть убейте, не пойму вопроса.
-- если к задаче подойти тупо в лоб, то лок должен быть статичным синглтоном, что из кода не очевидно, либо совсем не наблюдается.
Сервер по сути является классом. То есть в приложении может быть два сервера, каждый из которых имеет свой независимый список, который нужно блокировать внутри. Хотя вписать static private readonly Object locker = new Object(); пробовал - та же картина, что и без него.
Примечание:
-- опять же не видно внутренней реализации, может там зацикливание идет, типа клиент проставил лок и отправил себе сообщение
По сути ошибка была в этом, сяпки.