Pior (na verdade não vai funcionar)
Altere o modificador de acesso de counterparapublic volatile
Como outras pessoas mencionaram, isso por si só não é realmente seguro. A questão volatileé que vários threads em execução em várias CPUs podem e armazenarão em cache os dados e reordenarão as instruções.
Caso contrário volatile , e a CPU A incrementa um valor, a CPU B pode não ver esse valor incrementado até algum tempo depois, o que pode causar problemas.
Se for volatile, isso apenas garante que as duas CPUs vejam os mesmos dados ao mesmo tempo. Isso não os impede de intercalar suas operações de leitura e gravação, que é o problema que você está tentando evitar.
Segundo melhor:
lock(this.locker) this.counter++;
É seguro fazer isso (desde que você lembre-se de lockqualquer outro lugar que acessar this.counter). Impede que outros threads executem qualquer outro código que é protegido por locker. O uso de bloqueios também evita os problemas de reordenação de várias CPUs como acima, o que é ótimo.
O problema é que o bloqueio é lento e, se você reutilizar o lockerem outro lugar que não esteja realmente relacionado, poderá acabar bloqueando os outros threads sem motivo.
Melhor
Interlocked.Increment(ref this.counter);
Isso é seguro, pois efetivamente lê, incrementa e grava em 'um hit' que não pode ser interrompido. Por esse motivo, ele não afetará nenhum outro código, e você também não precisa se lembrar de bloquear em outro lugar. Também é muito rápido (como o MSDN diz, em CPUs modernas, isso geralmente é literalmente uma única instrução de CPU).
No entanto, não tenho muita certeza se ele contorna outras CPUs, reordenando as coisas ou se você também precisa combinar volátil com o incremento.
InterlockedNotes:
- MÉTODOS INTERLOCADOS SÃO CONCORRENTEMENTE SEGUROS EM QUALQUER NÚMERO DE CORES OU CPUs.
- Os métodos intertravados aplicam uma barreira completa em torno das instruções que eles executam, para que a reordenação não ocorra.
- Os métodos intertravados não precisam ou nem oferecem suporte ao acesso a um campo volátil , pois o volátil é colocado meia cerca em torno das operações em um determinado campo e o intertravado está usando a cerca completa.
Nota de rodapé: Para que serve realmente o volátil.
Como volatilenão impede esses tipos de problemas de multithreading, para que serve? Um bom exemplo é dizer que você tem dois threads, um que sempre grava em uma variável (por exemplo queueLength) e outro que sempre lê a mesma variável.
Se queueLengthnão for volátil, o segmento A pode gravar cinco vezes, mas o segmento B pode ver essas gravações como atrasadas (ou mesmo potencialmente na ordem errada).
Uma solução seria bloquear, mas você também pode usar volátil nessa situação. Isso garantiria que o segmento B sempre visse a coisa mais atualizada que o segmento A escreveu. Observe, no entanto, que essa lógica só funciona se você tiver escritores que nunca leem e leitores que nunca escrevem, e se o que você está escrevendo é um valor atômico. Assim que você fizer uma única leitura, modificação e gravação, precisará acessar as operações de bloqueio ou usar um bloqueio.