2010-05-17 13 views
10

Estoy escribiendo un programa que escucha un TcpClient entrante y maneja los datos cuando llegan. El método Listen() se ejecuta en un subproceso separado dentro del componente, por lo que debe ser seguro para la ejecución de subprocesos. Si yo break fuera de un dowhile loop mientras estoy dentro de una declaración lock(), ¿se liberará el bloqueo? Si no, ¿cómo lo logro?¿Qué sucede si saltas de una instrucción Lock()?

Gracias!

(Cualquier otro tipo de asesoramiento sobre el tema de asincrónicos sockets TCP es bienvenido también.)

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); 
} 
+6

Vas a la cárcel, luego serás liberado. – SwDevMan81

Respuesta

20

Sí. La instrucción de bloqueo se traduce en una cláusula try/finally. En C# 4, por ejemplo, una declaración de bloqueo de este modo:

lock(obj) 
{ 
    // body 
} 

podría traducir (taken from Eric Lippert's blog here) a:

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

Cuando la ejecución deja al alcance de la lock {}, se dará a conocer el cerrojo subyacente automáticamente. Esto sucederá sin importar cómo salga del alcance (break/return/etc), ya que la llamada a Monitor.Exit está envuelta, internamente, dentro del bloque finally de try/finally.

+0

Más rápido que yo, eres :) –

+0

Esto funcionará igual cuando se usa una instrucción GOTO dentro de una instrucción de bloqueo para ramificar fuera de la cerradura, ¿correcto? – eaglei22

1

vez que salga del lock{}, se desbloqueará lo que ha bloqueado (que es igual que una instrucción using en ese considerar). No importa dónde salgas (el comienzo, el final o el medio), es que dejaste el alcance de la cerradura. Piensa en lo que sucedería si plantearas una excepción en el medio.

+8

Muy importante para pensar qué pasaría si levantara una excepción en el medio. Algo excepcional e inesperado sale mal en medio de una operación crítica y ¿qué es lo primero que hace? * Libere el bloqueo * para que otro código pueda entrar y acceder al recurso que causó una excepción en el medio de una operación crítica. El hecho de que se liberen bloqueos en excepciones significa que tiene * más * trabajo por hacer, no * menos *. –

+0

Eso es cierto, no estaba pensando en eso de esa manera. Ese es un excelente punto. Mi pensamiento era qué pasaría si no soltaba la cerradura. Con una excepción, es impredecible donde en la pila quedará atrapado. Si alguien no tenía cuidado, la excepción podría burbujear varios niveles donde no hay indicios de que alguna vez se haya usado un candado. Ahora hay una excepción que tratar y un objeto bloqueado que impide que "potencialmente" ocurran otras operaciones exitosas. – kemiller2002

+0

El problema es que algo horrible puede suceder sin importar la estrategia que elijas. Las cerraduras y las excepciones se mezclan mal. Si tuviéramos memoria transaccional de software barata, sería un problema menor; simplemente podríamos retrotraer la memoria al estado en que se encontraba antes de ingresar la cerradura. Pero todavía no tenemos esa herramienta en nuestra caja de herramientas. –

3

Sí, se desbloqueará la cerradura. Puede usar ILDASM o Reflector para mirar el código generado real. La instrucción de bloqueo es una abreviatura del siguiente código (aproximadamente).

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

} 
finally { 
    Monitor.Exit(_client); 
} 

Observe que el bloque finally siempre se ejecuta.

+0

BTW - esto es correcto para <= C# 3, pero no exactamente lo que sucede en C# 4. –

+0

Sí, Eric Lippert documenta el cambio en C# 4. http://blogs.msdn.com/ericlippert/archive/2009/ 03/06/locks-and-exceptions-do-not-mix.aspx – Haacked

+3

Tenga en cuenta que el bloque finally * siempre * no se ejecuta siempre. El bloque finally solo se ejecuta * si el control abandona el try *. El control puede no dejar el intento; el bloque try puede contener un bucle infinito. Otro hilo podría fallar el proceso. Un administrador puede matar el proceso. Un hilo puede golpear la página de protección de la pila dos veces seguidas, reduciendo el proceso. Alguien podría desconectar la máquina. En todas estas situaciones, el bloque finally no se ejecuta. –

1

Porque me pidió otro consejo ... Noté que está anidando cerraduras. Esto, en sí mismo, no es necesariamente algo malo. Pero, es una de mis banderas rojas que cuido. Existe la posibilidad de un punto muerto si alguna vez adquiere esos dos bloqueos en un orden diferente en otra parte de su código. No digo que haya ningún problema con tu código. Es simplemente algo más a tener en cuenta porque es fácil equivocarse.

+0

¡Gracias por la advertencia! Me estoy asegurando de bloquear los objetos en el mismo orden, sin embargo, para que no se estanque. – dlras2

0

Para responder a la otra mitad de su pregunta:

Cualquier otro tipo de asesoramiento sobre el tema de asincrónicos de sockets TCP es bienvenida, así

En pocas palabras no me gestionar este a la moda demostrado por tu publicación original. Más bien busque ayuda de las clases System.Net.Sockets.TcpClient y System.Net.Sockets.TcpListener. Use las llamadas asincrónicas como BeginAcceptSocket (...) y BeginRead (...) y permita que ThreadPool haga su trabajo. Es realmente fácil de armar de esa manera.

Usted debe ser capaz de lograr todo el comportamiento del servidor que desee sin tener que codificar las temidas palabras "nuevo hilo" :)

Aquí es un ejemplo básico de la idea, menos la idea de apagado correcto, manejo de excepciones 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(); 
    } 
} 

para un ejemplo mucho más complicada de cómo hacerlo, vea la SslTunnel Library que escribí hace un tiempo.

Cuestiones relacionadas