sábado, 2 de marzo de 2013

Sincronización de threads

Threads sincronizados

Propósito de este documento:

Mostrar como programar con hilos y como sincronizarlos con SynchronizationContext para evitar algunos inconvenientes conocidos.
Evitar la ecxepción: “Operación no válida a través de subprocesos: Se tuvo acceso al control '…' desde un subproceso distinto a aquel en que lo creó.”

Si estás leyendo esto seguramente ya hayas intentado programar con threads o por lo menos tengas una idea de lo que es.

Supongamos una situación simple. Debemos crear un programa que dado 2 valores, los sume y muestre el resultado. Para ello abrimos el Visual Studio. En un proyecto nuevo de Windows Forms

Parte 1 – Una simple suma

Agregar:
3 TextBox (txtVal1, txtVal2, txtResult)
1 Botón (btnCalcular)

Programamos el botón Calcular de la siguiente forma

#Region "Calcular suma"

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles btnCalcular.Click
        txtResult.Text = Val(txtVal1.Text) + Val(txtVal2.Text)
    End Sub
#End Region
Esto es realmente sencillo.

Parte 2 – Suma con Thread v1

Ahora lo mismo pero utilizando un thread. Similar al caso anterior, usaremos un thread para realizar el cálculo y en lugar de mostrarlo en un txtResult vamos a mostrarlo con un msgbox().

Agregar:
2 TextBox (txtT1Val1, txtT1Val2)
1 Botón (btnT1Calcular)

En el código del formulario agregaremos lo siguiente

#Region "Calcular suma con thread v1"

    'Inicializo un thread.
    Private thT1 As System.Threading.Thread 'New System.Threading.Thread(AddressOf RealizarCalculo) With {.IsBackground = True}
    'Background determina que es un thread secundario.
    'Esto se pone para que si el thread se encuentra activo ejecutando alguna tarea y cerramos
    'el formulario principal (que se está ejecutando en el thread primario) el secundario
    'es también finalizado. En otras palabras, evita que al cerrar el formulario principal
    'se siga ejecutando el programa

    Private Sub btnT1Calcular_Click(sender As Object, e As EventArgs) Handles btnT1Calcular.Click
        thT1 = New System.Threading.Thread(AddressOf T1RealizarCalculo) With {.IsBackground = True}
        thT1.Start()
    End Sub

    Private Sub T1RealizarCalculo()
        Dim suma As Double = Val(txtT1Val1.Text) + Val(txtT1Val2.Text)
        MsgBox(suma)
    End Sub

#End Region

Parte 3 – Suma con Thread v2

Igual al anterior pero mostrando el resultado un TextBox en lugar de MsgBox()

Agregar:
3 TextBox (txtT2Val1, txtT2Val2, txtT2Result)
1 Botón (btnT2Calcular)

#Region "Calcular suma con thread v2"

    'Inicializo un thread.
    Private thT2 As System.Threading.Thread

    Private Sub btnT2Calcular_Click(sender As Object, e As EventArgs) Handles btnT2Calcular.Click
        thT2 = New System.Threading.Thread(AddressOf T2RealizarCalculo) With {.IsBackground = True}
        thT2.Start()
    End Sub

    Private Sub T2RealizarCalculo()
        Dim suma As Double = Val(txtT2Val1.Text) + Val(txtT2Val2.Text)
        txtT2Result.Text = suma
    End Sub

#End Region

La línea txtT2Result.Text = suma, da el error “Operación no válida a través de subprocesos: Se tuvo acceso al control”

Parte 4 – Suma con thread, sincronizando (v3)

Sincronizando threads.

Agregar:
3 TextBox (txtT3Val1, txtT3Val2, txtT3Result)
1 Botón (btnT3Calcular)

#Region "Calcular suma con thread v3 Sincronizando threads"

    'Contexto de sincronización actual
    Private T3syncContext As System.Threading.SynchronizationContext = System.Threading.SynchronizationContext.Current

    'Inicializo un thread.
    Private thT3 As System.Threading.Thread

    'Utilizo una variable auxiliar donde se va a guardar el cálculo
    Private T3Resultado As Decimal = 0D

    Private Sub btnT3Calcular_Click(sender As Object, e As EventArgs) Handles btnT3Calcular.Click
        thT3 = New System.Threading.Thread(AddressOf T3RealizarCalculo) With {.IsBackground = True}
        thT3.Start()
    End Sub

    Private Sub T3RealizarCalculo()
        T3Resultado = Val(txtT3Val1.Text) + Val(txtT3Val2.Text)


        'Threading.Thread.Sleep(1000)
        'Al descomentar la linea anterior puede ver como el thread queda detenido durante 1 segundo
        'sin que por ello se vea afectado el formulario principal que corre en otro thread. y luego
        'de ese lapso se continúa la ejecución (resultado es puesto en el textbox correspondiente)

        'Cuando el thread ya haya hecho lo suyo, ejecutamos un procedimiento
        'sincronizado el cual puede interactuar con el formulario sin problemas
        T3syncContext.Send(AddressOf T3ProcedimientoSincronizado, Nothing)
    End Sub


    ''' <summary>Procedimiento encargado de escribir el resultado en la caja de texto de resultado</summary>
    Private Sub T3ProcedimientoSincronizado(s As Double)
        txtT3Result.Text = T3Resultado
    End Sub

#End Region



Descargar este ejemplo aquí

No hay comentarios:

Publicar un comentario