Что произойдет, если вы выйдете из оператора Lock()?
Я пишу программу, которая прислушивается к входящему 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);
}
Ответы
Ответ 1
Да. Оператор блокировки преобразуется в предложение try/finally. В С# 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.
Ответ 2
Да, блокировка будет выпущена. Вы можете использовать ILDASM или Reflector для просмотра фактического сгенерированного кода. Оператор блокировки является сокращением для следующего кода (примерно).
Monitor.Enter(_client);
try
{
// do your stuff
}
finally {
Monitor.Exit(_client);
}
Обратите внимание, что блок finally всегда выполняется.
Ответ 3
Как только вы выйдете из lock{}
, он откроет блокировку, которую вы заблокировали (это похоже на использование инструкции в этом отношении). Неважно, где вы выходите (начало, конец или посередине), это означает, что вы полностью покинули область блокировки. Подумайте, что произойдет, если вы подняли исключение посередине.
Ответ 4
Потому что вы просили другие советы... Я заметил, что вы гнездовые блокировки. Это само по себе не обязательно плохо. Но это один из моих красных флагов, за которыми я слежу. Существует возможность блокировки, если вы когда-либо приобретете эти два замка в другом порядке в другой части вашего кода. Я не говорю, что с вашим кодом что-то не так. Это просто что-то еще, потому что легко ошибиться.
Ответ 5
Чтобы ответить на другую половину вопроса:
Любые другие рекомендации по теме асинхронных TCP-сокетов также приветствуются
Проще говоря, я бы не справился с этим в моде, продемонстрированном вашим оригинальным сообщением. Скорее обратитесь за помощью к классам System.Net.Sockets.TcpClient и к System.Net.Sockets.TcpListener. Используйте асинхронные вызовы, такие как BeginAcceptSocket (...) и BeginRead (...), и разрешите ThreadPool выполнять эту работу. Это действительно довольно легко собрать вместе.
Вы должны быть в состоянии достичь всего желаемого поведения сервера, никогда не кодируя страшные слова "новая тема":)
Вот базовый пример идеи, минус идея изящного выключения, ect обработки исключений:
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, которую я написал некоторое время назад.