Developer's Diary

13 marzo, 2010

Hilos de ejecución (I)

Filed under: .net, Programación — 3nk1 @ 1:57 AM

Una de las primeras cosas que aprendí fuera de mis estudios, fueron los hilos de ejecución. Mucho de los que puedan leer está entrada, sabrán a la perfección que son y para que pueden sernos útiles. No obstante, otros tantos pueden que no lo sepan.

Un hilo de ejecución según la wikipedia es lo siguiente:

Un hilo de ejecución, es una característica que permite a una aplicación realizar varias tareas a la vez (concurrentemente). Los distintos hilos de ejecución comparten una serie de recursos tales como el espacio de memoria, los archivos abiertos, situación de autenticación, etc. Esta técnica permite simplificar el diseño de una aplicación que debe llevar a cabo distintas funciones simultáneamente.

Que resumiendo viene a decir que con su uso podemos realizar varias tareas a la vez dentro de una aplicación.

Cuando no nos encontramos con un solo hilo de ejecución, por ejemplo, en MS-DOS, donde al ejecutar una función, no podíamos continuar trabajando con el equipo hasta que terminase la tarea que hicimos anteriormente. Recordais esas copias de archivos que podían mantener a nuestro equipo durante un largo rato ejecutándose o las búsquedas en muchas carpetas que tardaban una eternidad y mientras se hacía ese proceso, no podíamos continuar realizando ningún trabajo adicional. Se debía a una razón similar a esto.
Puede que mucha gente novicia en el mundillo siga sin verle la aplicación, por el mero hecho de que todavía no han realizado aplicaciones de grandes dimensiones. No obstante, solo debemos pensar en una comprobación que no termina hasta que ocurra algo en nuestro programa o cálculos de gran tamaño que no implican la necesidad de la espera de nuestra aplicación. Ese tipo de escenas son las que debemos solucionar mediante este tipo de procedimientos.

Un hilo o thread, conocido así en el idioma técnico. Puede resultar de mayor o menor desarrollo dentro del lenguaje en el que estamos trabajando. Pero el concepto principal es el uso de una clase interna de la aplicación.

Durante esta explicación realizaré dos pequeños ejemplos en Vb .net.

La manera de generar este tipo de procedimientos dentro de nuestra aplicación en el lenguaje .Net, no resulta excesivamente complicado. Solo tenemos que introducir dentro de nuestra clase el namespace System.Threading, con la clausula Imports.

A partir de ahí, podremos crear nuestro hilo, para hacerlo deberemos asignarle en el constructor el procedimiento que vamos a pasarle, normalmente, lo que realizamos es un procedimiento y no una función y no le pasamos parámetros a la misma. ¿Por qué?. La razón es sencilla, no es una función, porque no tenemos ninguna manera de recuperar el dato devuelto, la función se ejecuta pero no se indica la devolución, la manera más lógica de recuperar algún dato es devolviendo ese dato a una variable de la clase y no pasamos ningún valor, porque el procedimiento se va a realizar continuamente. Los valores que tuviesemos que pasarle deberían ser introducidos con anterioridad.

La principal utilidad que le doy a los mismos, es realizar una comprobación en nuestra aplicación que posteriormente notificaremos al resto de la aplicación mediante un evento, en el caso de que tratemos con programación orientada a eventos.  Es elevadamente importante, saber que la vida de ejecución de un hilo es lo que dure el procedimiento. Por lo tanto este terminara, cuando llegué al final del mismo.

Si nosotros le indicamos al hilo que realice una suma, esté tras iniciarlo, hará la suma y posteriormente “morirá”. Para mantenerlo con vida, sería necesario introducir un bucle con una condición, si queremos que esté ejecutándose durante todo el tiempo que tengamos la aplicación, simplemente introduciríamos un bucle infinito.

Otro detalle muy importante de los threads es el hecho de que estos, se ejecutan con la misma velocidad que la aplicación, por lo tanto, tener gran cantidad de hilos puede colapsar al mismo, no solo podemos darle mayor o menor prioridad a los mismos, en la mayoría de los casos. También podremos “dormirlos”, esto nos permite realizar la ejecución del procedimiento interno para posteriormente cuando lo termine, mantenerlo suspendido el tiempo que le hayamos indicado, para que después vuelva a iniciar su ejecución. La combinación de ambas prácticas es razonable.

Voy a explicar el primer ejemplo. En este primer trozo de código, voy a mostrar el uso del hilo para realizar el salto de un evento que posteriormente recogeremos en otro lugar, en otra situación explicare los eventos y su funcionalidad.

Code

Imports System.Threading

Public Class EjemploHilo1

Private hilo As New Thread(AddressOf proc_hilo)

Private b_hilo as boolean = True

Public event evento_prueba

Public sub EjemploThreading_Load(obj as system.object , e as system.eventargs) handles Me.load

hilo.start ‘Inicio el hilo

End Sub

Public sub EjemploThreading_Closed(obj as system.object , e as system.eventargs) handles Me.closed

b_hilo = False

End Sub

Public sub saltoelevento() handles Me.evento_prueba

msgbox(“El evento salto”) ‘Indico a la aplicación que el evento a saltado.

End Sub

Public sub proc_hilo

Dim a as integer = 0

While b_hilo

if(a = 4) then

a = 0

RaiseEvent evento_prueba ‘Levanto el evento.

Else

a = a + 1

End If

Thread.Sleep(100) ‘Mantengo el hilo “dormido” durante 100 milisegundos

End While

End Sub

End Class

Mediante este código, tendremos una ventana en la que saldrá aproximadamente cada 100 milisegundos, una caja de texto, indicando que el evento saltó. Aunque otro día nos dediquemos a los eventos, también se puede comprobar que su implementación en el código no ha sido relativamente difícil.

Para el segundo ejemplo vamos a realizar un procedimiento que posteriormente guardaremos en una variable privada de la clase. Debemos pensar que en ocasiones puede que realicemos calculos bastante complejos, si los realizamos en la aplicación, en el hilo principal. Nos econtraremos que la aplicación se quedaría “pillada”. Está es una de las razones, por las cuales se da el mítico “No responde” de Windows. Vamos a plantear, la consulta de unos datos, si la solicitud se realiza en el hilo principal, nos encontraremos con que la aplicación no volverá a responder hasta que se haya finalizado la consulta.

Para este ejemplo,  no vamos a introducir demasiado código de conexión con base de datos, supongamos que tenemos una clase de base de datos que nos devuelve la consulta.

Code

Imports System.Threading
Public Class Ejemplo_Hilo2

Private hilo As New Thread(AddressOf consulta)

Private b_hilo As Boolean = True

Private ds as new Dataset

Public event dataset_lleno

Private select as string = “SELECT * FROM prueba”

Public sub Ejemplo_Hilo2 (obj as system.object , e as system.eventargs) handles Me.load

hilo.start

End Sub

Public sub Mostrar_dato() handles me.dataset_lleno

msgbox(ds.tables(0).rows(0).item(0)

End Sub

Public Sub consulta()

ds = bd.consulta(select)

RaiseEvent dataset_lleno

End Sub

End Class

De nuevo en este caso volvemos a usar un evento para avisar de que nuestro hilo ha realizado lo que queríamos.  Aunque no necesario, es importante parapetarse en un evento, por la razón de que así, sabremos cuando nuestro proceso a finalizado correctamente. En otro caso, no tendríamos un aviso de que ocurre tal efecto.

Consejos y Tips

El objeto hilo en Vb .net, no tiene gran cantidad de métodos o propiedades, de hecho no tiene más de 15. De estas hay que mencionar algunas que os serán de mayor ayuda.

  • Start

Nos permitirá iniciar nuestro hilo.

  • IsAlive

Con está propiedad booleana, sabremos si el hilo en cuestión, continúa en ejecución o ya ha finalizado.

  • Abort

Podremos cancelar el hilo en cuestión instántaneamente. En caso de usar un hilo, con una comprobación de un bucle infinito, recomiendo usar el booleano que se encargue de que salga del bucle. Principalmente porque de esta manera no dejaremos un procedimiento a medio terminar, si no que finalizará el bloque de código y posteriormente saldrá del mismo.

  • Sleep

Mantiene latente el hilo al que estamos tratando, este método se puede llamar tanto dentro como fuera del procedimiento, aunque en el caso de llamarlo dentro del procedimiento usaremos la clase general, Thread y no la clase que construimos anteriormente, ya que esté se encuentra deprecated.

  • Priority

Establecemos la prioridad del hilo, de esa manera el sístema donde se está ejecutando le dará una mayor o menor prioridad al proceso que está realizando, a lo referente recursos.

Complicaciones y dificultades

Finalmente, lo peor de los hilos.

  • Podemos pensar que los hilos, pueden ser la manera profesional de los Timers. Eso fue lo que pensé yo en mi momento, pero no hay ni comparación. Si es verdad que podemos sustituir el 90% de tareas que anteriormente realizamos con un Timer. Pero son dos cosas distintas, aunque si debemos plantearnos el usar antes un hilo, principalmente porque es mucho más eficiente y consume una menos cantidad de recursos.
  • Resulta de mayor dificultad que es lo que está haciendo el hilo en todo momento,  además, hay que tener mucho ojo a la hora de debuggear una aplicación. Nosotros paramos la aplicación, pero inicialmente el hilo principal de la misma, el resto de hilos siguen funcionando y ejecutando las ordenes que tengan establecidas. Puede que cuando volvamos a ejecutar la aplicación normalmente nos encontremos con muchisimas ordenes enviados por este.
  • Al tratarse como comenté más arriba de una ejecución asíncrona con nuestro hilo principal y con el resto de hilos de ejecución al menos que no demos la orden de lo contrario. Nos podemos encontrar, con la disyuntiva de que algo en nuestra aplicación, principalmente un componente, se ve modificado en el mismo momento por dos hilos. Esto conlleva que la aplicación lance una excepción de tipo InvalidOperationException. Esto podemos solventarlo de dos maneras completamente distintas en Visual Studio.

La primera sería, introducir la clausula de CheckForIllegalCrossThreadCalls en el valor False o realizar métodos delegados. La primera solución es la más rápida y sencilla. Introducimos esa línea al inicio de la aplicación y listo. La segunda, requiere de mayor complejidad,. No obstante, como casi siempre en la vida, la vía fácil suele ser la más rastrera y así es. Ya que es menos eficiente que la segunda, al usar los delegados.

Hasta aquí la lección de hoy, sobre los hilos de ejecución. Espero que haya sido productiva, es posible que me equivoque en algunos aspectos aquí explicados, de todas formas, ya podeis tener una visión más clara de su utilidad y función.

Mañana enseñare a usar un poco los delegados en función de los hilos y también algunos trucos para la programación con hilos.

Anuncios

8 comentarios »

  1. Y la segunda parte? Es el primer lugar donde encuentro información clara y sin complicaciones

    Comentario por arim — 15 diciembre, 2012 @ 5:28 PM

    • No se crearon más entradas respecto a los hilos. Pero puedes dejar aquí tus dudas e intentaremos respondertelas.
      Un saludo.

      Comentario por 3nk1 — 20 diciembre, 2012 @ 2:04 PM

  2. ¿Cómo se puede controlar el uso de multi-thread de un mismo proceso? El problema que encuentro es que se llaman a las mismas funciones con los valores de cada hilo y por ejemplo se ejecuta hilo1 con valores de hilo2.

    Comentario por Javier — 11 febrero, 2013 @ 1:12 PM

    • Para ello deberías usar monitores. Es decir que el acceso de la variable solo se dejará permitida para un nuevo hilo, cuando este ya finalizó. Si continuas teniendo dudas, podría publicar un ejemplo aquí.

      Comentario por 3nk1 — 16 febrero, 2013 @ 4:55 PM

      • Supongo que te refieres a la utilización de SyncLock. El problema es que con esto se paraliza la multi-tarea. Lo que busco es crear hilos de objetos independientes que aunque llamen a los mismos métodos, lo hagan como instancias separadas sin interferirse entre ellos.

        Comentario por Javier — 18 febrero, 2013 @ 8:00 AM

  3. De igual manera me topo con los líos que se cruza la información de los hilos al momento de dar las respuestas a cada terminal conectada, hay alguna manera de aislarlos a cada uno?? Tengo esto :

    Private Sub ReadCallback(ByVal ar As IAsyncResult)
    Dim content As String = String.Empty

    Dim state As StateObject = CType(ar.AsyncState, StateObject)
    Dim handler As Socket = state.workSocket

    ‘lee los datos desde el cliente
    Dim bytesRead As Integer
    Dim ip As String = String.Empty
    Try
    ‘ ip = System.Net.IPAddress.Parse(CType(handler.RemoteEndPoint, IPEndPoint).Address.ToString).ToString
    If CType(ar.AsyncState, StateObject).workSocket.Ttl = 0 Then

    WindowsEventLog.CreateError(String.Format(“Desconectado sin paquetes recibidos ip={0}”, ip), 1322)
    End If

    bytesRead = handler.EndReceive(ar)
    Catch ex As Exception
    WindowsEventLog.CreateError(String.Format(“Desconectado {0} IP{1}”, ex.Message, ip), 1322)
    bytesRead = 0
    End Try

    If bytesRead > 0 Then
    ‘state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead))
    state.sb = Encoding.ASCII.GetString(state.buffer, 0, bytesRead)
    content = state.sb.ToString()
    ‘If content.IndexOf(“”) > -1 Then
    ‘Send(handler, content)
    ‘Else
    ‘No todos los datos, recive mas
    RaiseEvent DatosRecividos(handler, content)
    If handler.Connected = True Then
    handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, New AsyncCallback(AddressOf ReadCallback), state)
    End If
    Else
    handler.Shutdown(SocketShutdown.Both)
    handler.Close()
    End If

    End Sub

    Al invocar RaiseEvent DatosRecividos(handler, content) se sobre escriben las clases que se manejan en DatosRecividos….

    Comentario por Fermin_Couoh — 31 mayo, 2013 @ 11:05 PM

    • Hola Fermin_Couoh,

      Ante todo, gracias por interesarte y pedirnos ayuda. Te recomiendo que la proxima vez te pases por nuestro nuevo blog, te dejo el link con el mismo post donde has dejado el comentario aquí.

      Respecto a tu duda, por lo que puedo ver. Creo que puede ser posible a que todos los clientes van agregados al mismo manejador de eventos de DatosRecibidos y por eso se dice que se sobrescribe. Por lo que puedo ver tu objeto handler es el socket cliente. Por lo que deberías desde el control del evento, hacer distinción desde allí y así poder controlar que viene de cada socket. No sé si me explico bien.

      Realmente deben ir llegandote distintos callback al evento DatosRecibidos, de cada uno de las llamadas.

      No sé si con eso he podido resolver tu duda, si no estamos por aquí para echarte un cable.

      Comentario por 3nk1 — 1 junio, 2013 @ 1:50 PM

  4. Si gracias por responder no me queda muy claro con lo que me respondes esta es la definición de la función DatosRecibidos

    Public Event DatosRevicidos(ByVal handler As Socket, ByVal datos As String)

    Esta definición esta dentro de la misma clase “SocketServer” donde tengo las funciones “ReadCallback”,”AcceptCallback”,”SendCallback”; la asignación de valores devueltos por el Socket la hago en otra clase “ClsPrincipal” con esto:

    Private Sub Servidor_DatosRecibidos(ByVal handler As System.Net.Sockets.Socket, ByVal datos As String) Handles Servidor.DatosRevicidos

    System.Threading.Thread.CurrentThread.CurrentCulture = New System.Globalization.CultureInfo(“es-MX”)

    ‘Comprobar que llegaron todos los datos

    Dim Estructura1 As New ClsOperaciones

    End Sub

    Entonces una vez recibidos los datos hago diferentes operaciones y al final imprimo los tickets de venta, el detalle es que se esta cruzando los tickets de las diferentes cajas es decir a veces devuelve por ejemplo el ticket 1000 con información del ticket 1001; es decir con los nuevos datos que entraron, no siempre pasa supongo que es cuando se satura el server y queda bajo el rendimiento por tanta llamada.

    Estuve investigando y mencionan que la opción seria manejar delegados para poder meterlos en un hilo interno, ya que después que llega a la función “DatosRecibidos”
    tengo diferentes banderas según la operación a realizar por ejemplo: Reimpresión del Ticket, Facturación, registro de la venta e impresión del ticket, entonces cada uno de estos tienen su propia función la idea que tengo es meter en delegados estas funciones para poder aislarlos.

    Comentario por Fermin_Couoh — 3 junio, 2013 @ 4:54 PM


RSS feed for comments on this post. TrackBack URI

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: