#multithreading #.net-core
#многопоточность #.net-core
Вопрос:
Я подключаю сетевой сервер с сокетами к фоновому процессу с помощью Task.Run
. Я сохраняю Task
в поле внутри класса server, чтобы в будущем, если понадобится, я мог отменить его. Как только Task
начнется, я затем загружаю игровой движок с состоянием, которое выполняется синхронно.
private void ListenForConnection()
{
this.listeningTokenSource = new CancellationTokenSource();
this.listeningTask = Task.Run(async () =>
{
while (!this.IsDeleted)
{
Socket clientSocket = await this.serverSocket.AcceptAsync();
await this.CreatePlayerConnection(clientSocket);
}
}, this.listeningTokenSource.Token);
}
private async Task CreatePlayerConnection(Socket clientConnection)
{
IServerConnection playerConnection = await this.connectionFactory.CreateConnection(clientConnection);
IPlayer player = await this.playerFactory.CreatePlayer(playerConnection);
if (playerConnection is TcpConnection tcpConnection)
{
tcpConnection.SetPlayer(player);
}
this.connectedPlayers.Add(player);
await this.PlayerConnected(player);
await player.ExecuteCommand(this.InitialCommand);
}
В этом случае, если проигрыватель подключается к серверу, я передаю сокет в IServerConnection
экземпляр и назначаю его проигрывателю. По мере того, как клиентские команды отправляются через сокет, IServerConnection
для этого клиент получает его, анализирует его, а затем где-то в конвейере он захочет изменить состояние игры за пределами этого IServerConnection
; взаимодействуя с объектами, запущенными в движке (возможно, даже в другом потоке самостоятельно).
Все это происходит в netcoreapp2.0
консольном приложении.
Столкнусь ли я с проблемами с клиентским сокетом, возникающим в результате фоновой операции, когда он переходит в состояние касания, которое находится в другом потоке? В WPF и aspnet я использовал SynchronizationContext в прошлом, но в консольных приложениях этого нет.
Каков чистый способ убедиться, что я не захожу в тупик везде, без необходимости разбрасывать блокировки повсюду. Могу ли я захватить текущий поток перед запуском ListenForConnection()
задачи, и состояние маршалирования изменится обратно на этот исходный поток?
Комментарии:
1. Насколько я понимаю, это не
socket
проблема, а проблема общего ресурса. В вашем случае это был бы движок игры, который менялся бы разными потоками. Если несколько клиентов изменят общий ресурс, я бы обернул свой движок / общий ресурс в объект и использовал блокировку внутри этой оболочки всякий раз, когда изменяется значение.
Ответ №1:
С моей точки зрения, в конечном итоге вы всегда будете иметь общий ресурс, который будет обновляться разными потоками.Итак, использование lock
— это правильный путь.Однако, если вы обернете свой ресурс и свою блокировку в объект, вы сможете сохранить все synchronization
операции локально.
public class Resource{
public int Value{get;set;}
}
public class Engine
{
private Resource commonResource=new Resource();
private object @lock=new object();
public void ChangeResource(int newvalue)
{
lock(@lock)
{
this.commonResource.Value=newvalue;
}
}
}
public class Client{
private Engine engine;
public Client(Engine engine)
{
this.engine=engine;
}
public void ChangeResource(int newValue){
this.engine.ChangeResource(newvalue);
}
}
P.S Я не знаю, как вы используете свой движок / оболочку (независимо от того, что, по вашим словам, клиенты вносят изменения), поэтому я просто внедрил это во всех клиентах.Вы также могли бы использовать singleton
.
Если вы выполняете async
операции, рассмотрите возможность использования SempahoreSlim вместо блокировки.
Комментарии:
1. В вашем примере это было бы больше похоже на
Client
экземпляр, указывающий свойствам вEngine
, что делать. Напримерforeach(IPlayer player in Engine.Players) { player.World.Zone.SetTime(newTime); }
. Мне пришлось быlock
работать со свойствами, с которыми мой вызовZone
взаимодействует, верно? Этот шаблон довольно распространен в моей кодовой базе и потребовал бы от меня блокировки почти всех свойств моих объектов. Я думал вместо этого опубликовать на шине от клиента. Что-то вродеbus.Publish(new ChangeZoneTimeMessage(newTime))
. Шина принудительно передаст его в основной поток.2. Ну, если вы, допустим, создадите шину, на которой все клиенты будут публиковать изменения, и из этой шины поток с
engine
будетdequeue
и применять изменения, тогда вам все равно придется сделатьbus
потокобезопасным . Действительно, вам просто нужно было бы заблокировать шину, а не свойства.3. ОК. Похоже, мне нужно выполнить некоторую перестройку. В моем движке нет игрового цикла (все это управляется событиями), который можно использовать для опроса о новых сообщениях в шине. Создание событий в многопоточном сценарии, вероятно, не то, что я хочу в этом случае. Спасибо
4. Я реализовал один раз
Reactive
очередь, которая, как вы говорите, получала бы события, и потребители подписывались бы на нее. Возможно, это могло бы вам помочь: исходный