Что произойдет, если вы выйдете из оператора Lock ()? - PullRequest
11 голосов
/ 18 мая 2010

Я пишу программу, которая слушает входящий 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);
}

Ответы [ 5 ]

21 голосов
/ 18 мая 2010

Да. Оператор блокировки переводится в предложение try / finally. Например, в C # 4 оператор блокировки выглядит так:

lock(obj)
{
    // body
}

примерно переводит ( взято из блога Эрика Липперта здесь ) в:

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

Когда выполнение покидает область действия lock {}, базовая блокировка будет снята автоматически. Это произойдет независимо от того, как вы выходите из области видимости (break / return / etc), поскольку вызов Monitor.Exit внутренне обернут внутри блока finally блока try / finally.

3 голосов
/ 18 мая 2010

Да, замок будет снят. Вы можете использовать ILDASM или Reflector, чтобы посмотреть на фактический сгенерированный код. Оператор блокировки является сокращением для следующего кода (примерно).

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

}
finally {
  Monitor.Exit(_client);
}

Обратите внимание, что блок finally всегда выполняется.

1 голос
/ 18 мая 2010

Потому что вы попросили другой совет ... Я заметил, что вы вкладываете замки. Само по себе это не обязательно плохо. Но это один из моих красных флагов, за которым я следю. Существует вероятность тупиковой ситуации, если вы когда-нибудь приобретете эти две блокировки в другом порядке в другой части вашего кода. Я не говорю, что с вашим кодом что-то не так. Это просто что-то, на что нужно обратить внимание, потому что легко ошибиться.

1 голос
/ 18 мая 2010

Как только вы выйдете из lock{}, он разблокирует то, что вы заблокировали (это похоже на оператор использования в этом отношении). Неважно, где вы выходите (начало, конец или середина), это то, что вы вообще покинули область блокировки. Подумайте о том, что произойдет, если вы подняли исключение в середине.

0 голосов
/ 18 мая 2010

Чтобы ответить на другую половину вашего вопроса:

Любой другой совет по вопросу асинхронных сокетов 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 , которую я написал некоторое время назад.

...