Маршалирование кода из задачи.Запуск в вызывающий поток

#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 очередь, которая, как вы говорите, получала бы события, и потребители подписывались бы на нее. Возможно, это могло бы вам помочь: исходный