如果您打破Lock()语句会发生什么?

11

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

谢谢!

(关于异步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);
}

9
你会被送进监狱,然后最终会被释放。 - SwDevMan81
5个回答

22

是的。lock语句会被翻译成try/finally子句。例如,在C# 4中,像这样使用lock语句:

转化为:

是的。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 {}的作用域时,底层锁将自动释放。这将发生在无论如何退出作用域(break/return等),因为对Monitor.Exit的调用被封装在try/finally的finally块内部。


当在锁语句内使用GOTO语句跳出锁时,这个过程是否是一样的呢? - eaglei22

3

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

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

}
finally {
  Monitor.Exit(_client);
}

请注意,finally块总是会被执行。


顺便提一下,这对于<= C#3是正确的,但在C#4中情况有所不同。 - Reed Copsey
是的,Eric Lippert 记录了 C#4 中的更改。http://blogs.msdn.com/ericlippert/archive/2009/03/06/locks-and-exceptions-do-not-mix.aspx - Haacked
4
请注意,finally块不总是执行。 finally块仅在控制权离开try时执行。控制权可能不会离开try;try块可能包含一个无限循环。另一个线程可能会FailFast该进程。管理员可能会终止该进程。一个线程可能连续两次触碰堆栈警戒页面,将该进程关闭。有人可能会拔掉机器。在所有这些情况下,finally块都不会执行。 - Eric Lippert
2
@Eric Lippert:你忘记了宇宙射线改变CPU电压的因素 :) - LBushkin
嗯,既然Eric Lippert在StackOverflow上,我必须在我的描述中更加精确。 :) - Haacked

2

因为你请求其他建议...我注意到你正在嵌套锁定。这本身并不一定是坏事。但是,这是我观察的红旗之一。如果你在代码的另一个部分以不同的顺序获取这两个锁定,就可能发生死锁。我不是说你的代码有什么问题。只是要注意,因为很容易出错。


谢谢你的提醒!我正在确保以相同的顺序锁定对象,这样就不会出现死锁。 - dlras2

1
一旦你退出lock{},它会解锁你已经锁定的内容(这与使用语句类似)。无论你在哪里退出(开头、结尾或中间),重要的是你完全离开了lock的作用范围。想象一下如果你在中间抛出异常会发生什么。

8
非常重要的是考虑一下,如果在代码运行中触发异常会发生什么。在关键操作的中间出现异常和意外错误,你会做什么?“释放锁”,以便其他代码可以进入并访问导致关键操作中出现异常的资源!锁的释放意味着你需要做的工作更多而不是更少 - Eric Lippert
那是真的,我没有这样想过。这是一个很好的观点。我的想法是,如果它没有释放锁会怎样。有了异常,它将无法预测地在堆栈的哪个位置被捕获。如果有人不小心,异常可能会冒泡到几个级别,而没有任何提示使用了锁。现在需要处理异常和一个被锁定的对象,这会阻止“潜在”的其他成功操作的发生。 - kemiller2002
问题在于,无论您选择什么策略,都可能发生可怕的事情。锁和异常混合使用效果不佳。如果我们拥有廉价的软件事务内存,这将不再是一个问题;我们可以简单地将内存回滚到进入锁之前的状态。但是我们还没有这个工具在我们的工具箱中。 - Eric Lippert

0

回答你问题的另一半:

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

简单来说,我不会像你原始帖子中所示的那样进行管理。相反,应该从System.Net.Sockets.TcpClient和System.Net.Sockets.TcpListener类中寻求帮助。使用BeginAcceptSocket(...)和BeginRead(...)等异步调用,并允许线程池完成其工作。这种方式真的很容易组合在一起。

你应该能够实现所有所需的服务器行为,而无需编写可怕的“new Thread”代码 :)

这里是一个基本的示例,不包括优雅关闭、异常处理等内容:

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 Library


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接