Threading

Multi Threading avançado: Queues – VB.NET

A um tempo atras me deparei na seguinte situação:

Eu precisava criar um aplicativo WPF para processamento e edição de imagens. Porém, se tratando de imagens vi que teria de usar Multi Threading para manter a interface do aplicativo funcional enquanto as imagens estivessem sendo processadas.

Ou seja, eu precisava carregar as imagens para a memória numa thread paralela (visto que o carregamento de imagens grandes é demorado) e posteriormente edita-las. Só que foi ai que eu me deparei com o grande problema. A edição das imagens tem que ser feita na mesma thread que as carregou, do contrário seria lançada uma Exceção de Cross Threading Operation.

E como resolver? Muito simples, mas demorei um pouco para pensar nisso…

Eu crio uma Thread e atribuo à ela um Loop infinito, e portanto, a Thread irá sempre continuar em execução. Aí eu crio uma Queue (Pilha) para guardar as tarefas que terei de realizar.

Então, a cada Loop na Thread eu verifico se há alguma tarefa na Queue e se houver executo ela. Dessa forma, eu consigo carregar e editar as imagens sempre usando a mesma Thread. Como? Simples! Cada vez que eu preciso carregar uma imagem eu envio a rotina de carregamento (um Delegate) para a Queue desta Thread e a imagem será carregada de forma assíncrona. E quando eu precisar editar a imagem, eu envio a rotina de edição (outro Delegate) para a Queue novamente e ela será executada na mesma Thread sem gerar nenhuma exceção!

Interessante, não?

Então eu peguei este conceito e montei uma classe. Eis o código:

Public Class ActionQueueThread

#Region "Constructor"

    Public Sub New()
        _Queue = New ActionQueue
        _waitingTime = 50
        _Thread = New Thread(New ThreadStart(AddressOf Queue))
        _Thread.SetApartmentState(ApartmentState.MTA)
        _Thread.Priority = ThreadPriority.Normal
        _Thread.IsBackground = False
    End Sub

    Public Sub New(Name As String, WaitingTime As Long)
        _Queue = New ActionQueue
        _waitingTime = WaitingTime
        _Thread = New Thread(New ThreadStart(AddressOf Queue))
        _Thread.Name = Name
        _Thread.SetApartmentState(ApartmentState.MTA)
        _Thread.Priority = ThreadPriority.Normal
        _Thread.IsBackground = False
    End Sub

#End Region

#Region "Fields"

    Private _Thread As Thread
    Private _Queue As ActionQueue
    Private _State As ActionQueueThreadState = ActionQueueThreadState.Stopped
    Private _waitingTime As Long

#End Region

#Region "Properties"

    Public Property Name As String
        Get
            Return _Thread.Name
        End Get
        Set(value As String)
            _Thread.Name = value
        End Set
    End Property

    Public ReadOnly Property State As ActionQueueThreadState
        Get
            Return _State
        End Get
    End Property

    Public Property WaitingTime As Long
        Get
            Return _waitingTime
        End Get
        Set(value As Long)
            _waitingTime = value
        End Set
    End Property

#End Region

#Region "Methods"

    Private Sub Queue()
        Do
            If _State = ActionQueueThreadState.Stopped Then Exit Do
            Dim action As Action = _Queue.Dequeue
            If action IsNot Nothing Then
                _State = ActionQueueThreadState.Running
                action.Invoke()
            Else
                _State = ActionQueueThreadState.Waiting
            End If
            Thread.Sleep(_waitingTime)
        Loop
    End Sub

    ''' <summary>
    ''' Initialize the Queue processing
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Start()
        If _State = ActionQueueThreadState.Running Or _State = ActionQueueThreadState.Waiting Then
            Throw New InvalidOperationException("The queue has already in execution.")
        Else
            _State = ActionQueueThreadState.Running
            _Thread.Start()
        End If
    End Sub

    ''' <summary>
    ''' Stops the Queue execution
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub [Stop]()
        If _State = ActionQueueThreadState.Stopped Then
            Throw New InvalidOperationException("The queue has already canceled.")
        Else
            _State = ActionQueueThreadState.Stopped
        End If
    End Sub

    ''' <summary>
    ''' Posts a action to the end of execution Queue
    ''' </summary>
    ''' <param name="Action">The action to execute</param>
    ''' <remarks></remarks>
    Public Sub Post(Action As Action)
        _Queue.Enqueue(Action)
    End Sub

#End Region

Public Class ActionQueue
    Inherits Queue(Of Action)
    Public Shadows Function Dequeue() As Action
        If Me.Count = 0 Then Return Nothing
        Return MyBase.Dequeue
    End Function
End Class

Public Enum ActionQueueThreadState
    Running
    Waiting
    Stopped
End Enum

End Class

Chamei-a ActionQueueThread.

Trabalhar com ela é muito simples. Primeiro você tem que instancia-la. Depois chame o método Start() para dar inicio a Thread. Use o método Post(Action as Action) para enviar uma rotina (Um Delegate do tipo Action) para a pilha de processamento da Thread. E ao final da execução do seu aplicativo, ou no momento que você achar melhor, chame o método Stop() para finalizar a Thread.

A propriedade WaitingTime define o tempo (em milissegundos) que o processador irá descansar entre o processamento de uma tarefa e outra. É muito importante que este valor seja maior que 0, do contrário, a thread irá demandar processamento mesmo quando não estiver processando nenhuma tarefa. Um valor ideal seria algo como 50 ou 100.

A propriedade State retorna o estado atual da Thread. Se ela já estiver parada ou se ainda não foi iniciada, irá retornar Stopped. Se estiver executando alguma tarefa, irá retornar Running. Caso estiver funcionando mas sem nenhuma tarefa para executar, irá retornar Waiting.

Bom, é isso! Espero que possa ser útil para alguém.

Anúncios