Синхронизация коллекций и прерывание задач

Я пишу небольшой многопоточный сетевой сервер. Все классические вещи: он прослушивает входящие соединения, принимает их и затем обслуживает их в разных потоках. Кроме того, этот сервер иногда должен будет перезапускаться, и для этого он должен: a) прекратить прослушивание, b) удалить все подключенные клиенты, c) настроить некоторые параметры / подождать, d) возобновить прослушивание.

Ну, я почти ничего не знаю о разработке многопоточных программ, поэтому я ищу помощи. Вот к чему я пришел (только основной материал):

class Server
{
    class MyClient
    {
        Server server;
        TcpClient client;
        bool hasToFinish = false;

        public MyClient(Server server, TcpClient client)
        {
            this.server = server;
            this.client = client;
        }

        public void Go()
        {
            while (!hasToFinish)
            {
                // do all cool stuff
            }
            CleanUp();
        }

        private void CleanUp()
        {
            // finish all stuff

            client.Close();
            server.myClients.Remove(this);
        }

        public void Finish()
        {
            hasToFinish = true;
        }
    }

    bool running = false;
    TcpListener listener;
    HashSet<MyClient> myClients = new HashSet<MyClient>();

    public void Start()
    {
        if (running)
            return;

        myClients.Clear();
        listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 1234);
        listener.Start();
        listener.BeginAcceptTcpClient(AcceptClient, this);
        running = true;
    }

    public void Stop()
    {
        if (!running)
            return;

        listener.Stop();
        foreach (MyClient client in myClients)
        {
            client.Finish();
        }
        myClients.Clear();
        running = false;
    }

    public void AcceptClient(IAsyncResult ar)
    {
        MyClient client = new MyClient(this, ((TcpListener)ar.AsyncState).EndAcceptTcpClient(ar));
        myClients.Add(client);
        client.Go();
    }
}

Это абсолютно неудовлетворительно. Синхронизации нет (я просто не знаю, где ее разместить!), И вызов Server.Stop () не приводит к немедленной остановке MyClient-s. Как я могу исправить эти проблемы?

1 ответ

Решение

Код выглядит достаточно чистым, мы можем сделать его поточно-ориентированным с помощью простых модификаций.

Проблема состоит из трех частей: "клиент", "сервер" и взаимодействие клиент-сервер.

Сначала клиент, метод Go() вызывается одним потоком (назовем его A), а метод Finish() вызывается другим потоком (B). Когда поток B изменяет поле hasToFinish, поток A может не сразу увидеть изменение, потому что переменная может быть кэширована в кэше ЦП. Мы можем исправить это, сделав поле hasToFinish "volatile", что заставит поток B публиковать изменение переменной в потоке A при обновлении.

Теперь класс сервера. Я рекомендую вам синхронизировать три метода на экземпляре "Server", как в примере ниже. Это гарантирует, что Start и Stop вызываются последовательно и переменные, которые они изменяют, публикуются в потоках.

Взаимодействие клиент-сервер также необходимо учитывать. В вашем коде Клиент удаляет свою ссылку с Сервера, но сервер удаляет все ссылки клиентов, когда Finish() любым способом. Это выглядит избыточным для меня. Если мы сможем удалить часть кода в клиенте, нам не о чем беспокоиться. Если по какой-либо причине вы решите сохранить логику в клиенте, а не на сервере, создайте открытый метод RemoveClient(клиент-клиент) в классе Server и синхронизируйте его с экземпляром Server. Затем позвольте клиенту вызывать этот метод вместо непосредственного манипулирования HashSet.

Я надеюсь, что это решит вашу проблему.

public void Start()
{
  lock(this) 
  {
    if (running)
        return;

    myClients.Clear();
    listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 1234);
    listener.Start();
    listener.BeginAcceptTcpClient(AcceptClient, this);
    running = true;
  }
}

public void Stop()
{
  lock(this)
  {
    if (!running)
        return;

    listener.Stop();
    foreach (MyClient client in myClients)
    {
        client.Finish();
    }
    myClients.Clear();
    running = false;
  }
}

public void AcceptClient(IAsyncResult ar)
{
  lock(this)
  {
    MyClient client = new MyClient(this, ((TcpListener)ar.AsyncState).EndAcceptTcpClient(ar));
    myClients.Add(client);
    client.Go();
  }
}
Другие вопросы по тегам