Instrução SyncLock

Adquire um bloqueio exclusivo para um bloco de instruções, antes de executar o bloco.

Sintaxe

SyncLock lockobject  
    [ block ]  
End SyncLock  

Partes

lockobject
Obrigatórios. Expressão que avalia uma referência de objeto.

block
Opcional. Bloco de instruções que devem ser executadas quando o bloqueio é adquirido.

End SyncLock
Termina um bloco SyncLock.

Comentários

A instrução SyncLock garante que vários threads não executam o bloco de instruções ao mesmo tempo. SyncLock impede que cada thread entre no bloco até que nenhum outro thread o esteja executando.

O uso mais comum de SyncLock é proteger os dados para que não sejam atualizados por mais de um thread simultaneamente. Se as instruções que manipulam os dados precisarem ir para a conclusão sem interrupção, coloque-as dentro de um bloco SyncLock.

Um bloco de instruções protegido por um bloqueio exclusivo, às vezes, é chamado de seção crítica.

Regras

  • Ramificação. Você não pode ramificar em um bloco SyncLock de fora do bloco.

  • Valor do Objeto de Bloqueio. O valor de lockobject não pode ser Nothing. Você deve criar o objeto de bloqueio, antes de usá-lo em uma instrução SyncLock.

    Não é possível alterar o valor de lockobject durante a execução de um bloco SyncLock. O mecanismo exige que o objeto de bloqueio permaneça inalterado.

  • Você não pode usar o operador Await em um bloco SyncLock.

Comportamento

  • Mecanismo. Quando um thread acessa a instrução SyncLock, ele avalia a expressão lockobject e suspende a execução até que ela adquira um bloqueio exclusivo no objeto retornado pela expressão. Quando outro thread acessa a instrução SyncLock, ele não adquire um bloqueio até que o primeiro thread execute a instrução End SyncLock.

  • Dados Protegidos. Se lockobject for uma variável Shared, o bloqueio exclusivo impedirá que um thread em qualquer instância da classe execute o bloco SyncLock, enquanto qualquer outro thread o estiver executando. Isso protege os dados compartilhados em todas as instâncias.

    Se lockobject for uma variável de instância (não Shared), o bloqueio impedirá que um thread em execução na instância atual execute o bloco SyncLock, ao mesmo tempo que outro thread na mesma instância. Isso protege os dados mantidos pela instância individual.

  • Aquisição e Liberação. Um bloco SyncLock se comporta como uma construção Try...Finally em que o bloco Try adquire um bloqueio exclusivo no lockobject e o bloco Finally o libera. Por isso, o bloco SyncLock garante a liberação do bloco, independentemente de como você sai dele. Isso é verdade mesmo no caso de uma exceção não tratada.

  • Chamadas de Estrutura. O bloco SyncLock adquire e libera o bloqueio exclusivo chamando os métodos Enter e Exit da classe Monitor no namespace System.Threading.

Práticas de Programação

A expressão lockobject sempre deve ser avaliada para um objeto que pertence exclusivamente à classe. Você deve declarar uma variável de objeto Private para proteger os dados pertencentes à instância atual ou uma variável de objeto Private Shared para proteger os dados comuns a todas as instâncias.

Você não deve usar a palavra-chave Me para fornecer um objeto de bloqueio para dados de instância. Se o código externo à sua classe tiver uma referência a uma instância da sua classe, ele pode usar essa referência como objeto de bloqueio para um bloco SyncLock completamente diferente do seu, protegendo dados diferentes. Dessa forma, sua classe e a outra classe podem impedir uma à outra de executar os blocos SyncLock não relacionados. Da mesma forma, o bloqueio em uma cadeia de caracteres pode ser problemático, pois qualquer outro código no processo, que use a mesma cadeia de caracteres, compartilhará o mesmo bloqueio.

Você também não deve usar o método Me.GetType para fornecer um objeto de bloqueio para dados compartilhados. Isso ocorre porque GetType sempre retorna o mesmo objeto Type para determinado nome de classe. O código externo pode chamar GetType na classe e obter o mesmo objeto de bloqueio que você está usando. Isso resultaria em duas classes bloqueando uma à outra nos blocos SyncLock.

Exemplos

Descrição

O exemplo a seguir mostra uma classe que mantém uma lista simples de mensagens. Ele retém as mensagens em uma matriz e o último elemento usado dessa matriz em uma variável. O procedimento addAnotherMessage incrementa o último elemento e armazena a nova mensagem. Essas duas operações são protegidas pelas instruções SyncLock e End SyncLock, visto que depois que o último elemento tiver sido incrementado, a nova mensagem deve ser armazenada antes que qualquer outro thread incremente o último elemento novamente.

Se a classe simpleMessageList compartilhasse uma lista de mensagens entre todas as instâncias, as variáveis messagesList e messagesLast seriam declaradas como Shared. Nesse caso, a variável messagesLock também deve ser Shared, para que haja um único objeto de bloqueio usado por cada instância.

Código

Class simpleMessageList
    Public messagesList() As String = New String(50) {}
    Public messagesLast As Integer = -1
    Private messagesLock As New Object
    Public Sub addAnotherMessage(ByVal newMessage As String)
        SyncLock messagesLock
            messagesLast += 1
            If messagesLast < messagesList.Length Then
                messagesList(messagesLast) = newMessage
            End If
        End SyncLock
    End Sub
End Class

Descrição

O exemplo a seguir usa threads e SyncLock. Contanto que a instrução SyncLock esteja presente, o bloco de instruções será uma seção crítica e balance nunca se tornará um número negativo. Você pode comentar as instruções SyncLock e End SyncLock para ver o efeito de não usar a palavra-chave SyncLock.

Código

Imports System.Threading

Module Module1

    Class Account
        Dim thisLock As New Object
        Dim balance As Integer

        Dim r As New Random()

        Public Sub New(ByVal initial As Integer)
            balance = initial
        End Sub

        Public Function Withdraw(ByVal amount As Integer) As Integer
            ' This condition will never be true unless the SyncLock statement
            ' is commented out:
            If balance < 0 Then
                Throw New Exception("Negative Balance")
            End If

            ' Comment out the SyncLock and End SyncLock lines to see
            ' the effect of leaving out the SyncLock keyword.
            SyncLock thisLock
                If balance >= amount Then
                    Console.WriteLine("Balance before Withdrawal :  " & balance)
                    Console.WriteLine("Amount to Withdraw        : -" & amount)
                    balance = balance - amount
                    Console.WriteLine("Balance after Withdrawal  :  " & balance)
                    Return amount
                Else
                    ' Transaction rejected.
                    Return 0
                End If
            End SyncLock
        End Function

        Public Sub DoTransactions()
            For i As Integer = 0 To 99
                Withdraw(r.Next(1, 100))
            Next
        End Sub
    End Class

    Sub Main()
        Dim threads(10) As Thread
        Dim acc As New Account(1000)

        For i As Integer = 0 To 9
            Dim t As New Thread(New ThreadStart(AddressOf acc.DoTransactions))
            threads(i) = t
        Next

        For i As Integer = 0 To 9
            threads(i).Start()
        Next
    End Sub

End Module

Comentários

Confira também