Thread Safety em .NET – Diferença entre utilizar Monitor.Enter e Monitor.Exit e lock / Thread Safety in .NET–Difference Between using Monitor.Enter and Monitor.Exit and lock (PT/EN)
(PT)
Quando estamos a desenvolver soluções em .NET e necessitamos de garantir a segurança entre Threads de forma a manter a solução a funcionar de uma forma coerente. Duas das opções que temos são a utilização de Monitor ou lock. Mas o que acontece exactamente quando utilizamos um e ou outro? Qual é a diferença entre eles?
Básicamente a diferença é que o lock quando compilado emite um código que coloca o Monitor dentro de um bloco de try...finally. Por isso a diferença é que o lock na realidade fornece-nos um Monitor à prova de excepções, pois garante-nos que ainda que exista uma excepção o nosso lock vai ser retirado libertando o objecto.
Vamos então ver um exemplo:
Imaginem o seguinte código utilizando o lock.
1: object something = new object();
2: lock (something)
3: {
4: var query = new TargetProcessEntities()
.Bug.Where(bug => bug.BugID > 100);
5:
6: foreach (var bug in query)
7: {
8:
9: Console.WriteLine("{0}", bug.BugID);
10:
11: }
12: }
O código gerado será: (using .NET Reflector)
1: object CS$2$0000;
2: object something = new object();
3: bool <>s__LockTaken0 = false;
4: try
5: {
6: Monitor.Enter(CS$2$0000 = something,
ref <>s__LockTaken0);
7: IQueryable<Bug> query = from bug in
new TargetProcessEntities().Bug
8: where bug.BugID >= 100
9: select bug;
10: foreach (Bug bug in query)
11: {
12: Console.WriteLine("{0}", bug.BugID);
13: }
14: }
15: finally
16: {
17: if (<>s__LockTaken0)
18: {
19: Monitor.Exit(CS$2$0000);
20: }
21: }
Então o que podemos ver é que o lock que colocamos na linha 2 do código original deu lugar a um bloco try…finally como podemos ver nas linhas 4…6 e 14…21.
Por isso mesmo quando necessitarmos de utilizar bloquear recursos de forma a garantir a segurança entre threads e a coerência dos resultados da utilização de multi-threading, deveremos utilizar o lock ao invés de Monitor.Enter e Monitor.Exit, uma vez que o lock na realidade nos fornece uma forma optimizada da utilização da classe Monitor.
Gostaria de agradecer ao Richard Blewett pela sua excelente explicação.
(EN)
When we want to develop ThreadSafe solutions .NET two of the options that we can use are Monitor and lock. But what exactly happens when we use one and another? What are the differences between one and the other?
Basically the difference is that lock really emits code that places a a Monitor inside a try...finally block. So the real difference is that lock really gives us a exception proof Monitor, since it makes sure that it gets free of that locking even if an exception occurs.
So lets see a sample of that:
Imagine this as the code that we write using lock.
1: object something = new object();
2: lock (something)
3: {
4: var query = new TargetProcessEntities()
.Bug.Where(bug => bug.BugID > 100);
5:
6: foreach (var bug in query)
7: {
8:
9: Console.WriteLine("{0}", bug.BugID);
10:
11: }
12: }
The generated code will be: (using .NET Reflector)
1: object CS$2$0000;
2: object something = new object();
3: bool <>s__LockTaken0 = false;
4: try
5: {
6: Monitor.Enter(CS$2$0000 = something,
ref <>s__LockTaken0);
7: IQueryable<Bug> query = from bug in
new TargetProcessEntities().Bug
8: where bug.BugID >= 100
9: select bug;
10: foreach (Bug bug in query)
11: {
12: Console.WriteLine("{0}", bug.BugID);
13: }
14: }
15: finally
16: {
17: if (<>s__LockTaken0)
18: {
19: Monitor.Exit(CS$2$0000);
20: }
21: }
So as we can see the lock that we placed in the original code at line 2, really gave place to a try…finally block like we can see at lines 4…6 and 14…21.
Basically when we need to use locks in order to have Thread Safety, than we should use lock instead of Monitor.Enter and Monitor.Exit, since lock really gives us an optimized way to use the Monitor class.
I’d like to thank Richard Blewett for his great explanation of it.