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 serNothing
. Você deve criar o objeto de bloqueio, antes de usá-lo em uma instruçãoSyncLock
.Não é possível alterar o valor de
lockobject
durante a execução de um blocoSyncLock
. 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ãolockobject
e suspende a execução até que ela adquira um bloqueio exclusivo no objeto retornado pela expressão. Quando outro thread acessa a instruçãoSyncLock
, ele não adquire um bloqueio até que o primeiro thread execute a instruçãoEnd SyncLock
.Dados Protegidos. Se
lockobject
for uma variávelShared
, o bloqueio exclusivo impedirá que um thread em qualquer instância da classe execute o blocoSyncLock
, 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ãoShared
), o bloqueio impedirá que um thread em execução na instância atual execute o blocoSyncLock
, 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çãoTry...Finally
em que o blocoTry
adquire um bloqueio exclusivo nolockobject
e o blocoFinally
o libera. Por isso, o blocoSyncLock
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étodosEnter
eExit
da classeMonitor
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