简体   繁体   English

同步收集和中止任务

[英]Synchronizing collections and aborting tasks

I am writing a small multi-threaded network server. 我正在写一个小型的多线程网络服务器。 All classical stuff: it listens for incoming connections, accepts them and then serves them in different threads. 所有经典的东西:它侦听传入的连接,接受它们,然后在不同的线程中服务它们。 Also, this server sometimes will have to restart, and to do so it must a) stop listening, b) kick out all connected clients, c) adjust some settings/wait, d) resume listening. 另外,此服务器有时必须重新启动,并且必须重新启动,a)停止监听,b)踢出所有连接的客户端,c)调整一些设置/等待,d)恢复监听。

Well, I pretty much don't know a thing about developing multi-threaded programs, so I am looking for help. 好吧,我对开发多线程程序一无所知,因此我正在寻求帮助。 Here's what I came to (core stuff only): 这就是我的目的(仅限核心内容):

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();
    }
}

It's absolutely unsatisfactory. 绝对不能令人满意。 There is no sychronizing (I just don't know where to put it!), and calling Server.Stop() doesn't make MyClient-s to stop immediately. 没有同步(我只是不知道放在哪里!),并且调用Server.Stop()不会使MyClient-s立即停止。 How do I fix these problems? 我该如何解决这些问题?

The code looks quite clean, we can make it thread-safe with simple modifications. 该代码看起来很干净,我们可以通过简单的修改使其成为线程安全的。

There are three parts of the problem, the "client", the "server" and the client-server interaction. 问题分为三个部分,“客户端”,“服务器”和客户端-服务器交互。

Client first, the Go() method is invoked by one thread (let's call it A) and the Finish() method is invoke by another thread (B). 客户端首先,一个线程调用Go()方法(我们称其为A),而另一个线程(B)调用Finish()方法。 When thread B modify hasToFinish field, thread A may not see the modification immediately because the variable may be cached in the CPU cache. 当线程B修改hasToFinish字段时,线程A可能不会立即看到修改,因为该变量可能已缓存在CPU缓存中。 We can fix it by making hasToFinish field "volatile", which force thread B to publish the variable change to thread A when update. 我们可以通过将hasToFinish字段设置为“ volatile”来修复它,该字段强制线程B在更新时将变量更改发布到线程A。

Now the server class. 现在是服务器类。 I recommend you to synchronise three methods on the "Server" instance like the example below. 我建议您像下面的示例一样在“服务器”实例上同步三种方法。 It makes sure Start and Stop are called sequentially and the variables they changes are published across threads. 确保启动和停止被顺序调用,并且它们所更改的变量将在线程之间发布。

The client-server interaction need to be addressed as well. 客户端-服务器交互也需要解决。 In your code, Client remove its reference from the Server but the server clear all clients references when Finish() any way. 在您的代码中,客户端从服务器中删除其引用,但是当使用Finish()时,服务器会清除所有客户端引用。 It looks redundant to me. 在我看来,这是多余的。 If we can remove the part of code in client, we have nothing to worry about. 如果我们可以删除客户端中的代码部分,则无需担心。 If you choose to keep the logic in the client rather in the server for what ever reason, create a public method call RemoveClient(Client client) in the Server class and synchronise it against the Server instance. 如果出于某种原因选择将逻辑保留在客户端而不是服务器中,请在Server类中创建一个调用RemoveClient(Client client)的公共方法,并将其与Server实例同步。 Then let the client to invoke this method instead of manipulating the HashSet directly. 然后,让客户端调用此方法,而不是直接操作HashSet。

I hope this solve your problem. 我希望这能解决您的问题。

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();
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM