正在同步集合并中止任务



我正在编写一个小型多线程网络服务器。所有经典的东西:它监听传入的连接,接受它们,然后在不同的线程中为它们提供服务。此外,此服务器有时必须重新启动,为此,它必须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();
    }
}

这绝对不能令人满意。没有同步(我只是不知道该放在哪里!),也没有调用服务器。Stop()不会使MyClient-s立即停止。如何解决这些问题?

代码看起来很干净,我们可以通过简单的修改使其线程安全。

问题有三个部分,"客户端"、"服务器"和客户端-服务器交互。

首先,一个线程调用Go()方法(我们称之为A),另一个线程(B)调用Finish()方法。当线程B修改hasToFinish字段时,线程A可能不会立即看到修改,因为变量可能缓存在CPU缓存中。我们可以通过将hasToFinish字段设置为"volatile"来修复它,这将迫使线程B在更新时将变量更改发布到线程A。

现在是服务器类。我建议您在"服务器"实例上同步三个方法,如下例所示。它确保按顺序调用Start和Stop,并确保它们更改的变量跨线程发布。

客户端-服务器交互也需要解决。在您的代码中,客户端从服务器中删除其引用,但服务器在Finish()时以任何方式清除所有客户端引用。这在我看来是多余的。如果我们能删除客户端中的代码部分,我们就不用担心了。如果出于任何原因选择将逻辑保留在客户端而不是服务器中,请在server类中创建一个名为RemoveClient(客户端-客户端)的公共方法,并将其与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();
  }
}

最新更新