11

我正在编写一个程序,它监听传入的 TcpClient 并在数据到达时处理它。该Listen()方法在组件内的单独线程上运行,因此它需要是线程安全的。如果我在语句中时break退出do while循环lock(),锁会被释放吗?如果没有,我该如何做到这一点?

谢谢!

(也欢迎任何其他关于异步 TCP 套接字主题的建议。)

private void Listen()
{
    do
    {
        lock (_clientLock)
        {
            if (!_client.Connected) break;
            lock (_stateLock)
            {
                if (!_listening) break;
                if (_client.GetStream().DataAvailable) HandleData();
            }
        }
        Thread.Sleep(0);
    } while (true);
}
4

5 回答 5

21

是的。lock 语句转换为 try/finally 子句。例如,在 C# 4 中,这样的 lock 语句:

lock(obj)
{
    // body
}

大致翻译为(取自 Eric Lippert 的博客):

bool lockWasTaken = false;
var temp = obj;
try 
{ 
    Monitor.Enter(temp, ref lockWasTaken); 
    { 
       // body 
    }
}
finally 
{ 
    if (lockWasTaken) 
        Monitor.Exit(temp); 
}

当执行离开范围时lock {},底层锁将自动释放。无论您如何退出范围(中断/返回/等),都会发生这种情况,因为对 Monitor.Exit 的调用在内部包装在 try/finally 的 finally 块内。

于 2010-05-17T20:18:34.910 回答
3

是的,锁将被释放。您可以使用 ILDASM 或 Reflector 查看实际生成的代码。lock 语句是以下代码的简写(大致)。

Monitor.Enter(_client);
try
{
  // do your stuff

}
finally {
  Monitor.Exit(_client);
}

注意 finally 块总是被执行。

于 2010-05-17T20:20:45.767 回答
1

一旦你退出lock{},它就会解锁你锁定的东西(这就像一个 using 语句)。无论你从哪里退出(开始、结束或中间),你完全离开了锁的范围。想想如果你在中间抛出异常会发生什么。

于 2010-05-17T20:17:15.647 回答
1

因为您要求其他建议...我注意到您正在嵌套锁。这本身并不一定是坏事。但是,这是我要提防的危险信号之一。如果您在代码的另一部分以不同的顺序获取这两个锁,则可能会出现死锁。我并不是说你的代码有什么问题。这只是其他需要注意的事情,因为它很容易出错。

于 2010-05-17T20:37:07.450 回答
0

要回答您问题的另一半:

也欢迎有关异步 TCP 套接字主题的任何其他建议

简而言之,我不会以您原始帖子所展示的方式管理此问题。而是从 System.Net.Sockets.TcpClient 和 System.Net.Sockets.TcpListener 类寻求帮助。使用 BeginAcceptSocket(...) 和 BeginRead(...) 之类的异步调用,并允许 ThreadPool 完成它的工作。以这种方式组合起来真的很容易。

您应该能够实现您想要的所有服务器行为,而无需编写可怕的单词“新线程”:)

这是这个想法的一个基本示例,减去优雅关闭、异常处理等的想法:

public static void Main()
{
    TcpListener listener = new TcpListener(new IPEndPoint(IPAddress.Loopback, 8080));
    listener.Start();
    listener.BeginAcceptTcpClient(OnConnect, listener);

    Console.WriteLine("Press any key to quit...");
    Console.ReadKey();
}

static void OnConnect(IAsyncResult ar)
{
    TcpListener listener = (TcpListener)ar.AsyncState;
    new TcpReader(listener.EndAcceptTcpClient(ar));
    listener.BeginAcceptTcpClient(OnConnect, listener);
}

class TcpReader
{
    string respose = "HTTP 1.1 200\r\nContent-Length:12\r\n\r\nHello World!";
    TcpClient client;
    NetworkStream socket;
    byte[] buffer;

    public TcpReader(TcpClient client)
    {
        this.client = client;
        socket = client.GetStream();

        buffer = new byte[1024];
        socket.BeginRead(buffer, 0, 1024, OnRead, socket);
    }

    void OnRead(IAsyncResult ar)
    {
        int nBytes = socket.EndRead(ar);
        if (nBytes > 0)
        {
            //you have data... do something with it, http example
            socket.BeginWrite(
                Encoding.ASCII.GetBytes(respose), 0, respose.Length, null, null);

            socket.BeginRead(buffer, 0, 1024, OnRead, socket);
        }
        else
            socket.Close();
    }
}

有关如何执行此操作的更复杂的示例,请参阅我不久前编写的SslTunnel 库

于 2010-05-17T23:31:34.607 回答