16

Here is an example of an exception happening inside a lock, with a try-catch block.

int zero = 0;
int j = 10;

lock (sharedResource.SyncRoot)
{
    try
    {
        j = j / zero;
    }
    catch (DivideByZeroException e)
    {
        // exception caught but lock not released
    }
}

How do I safely release this lock in the catch?

Justin Tanner
  • 14,062
  • 17
  • 82
  • 103
  • sidenote from creating the IL for proving this: the above won't even compile because of the constant div/zero error :) – annakata Mar 12 '09 at 17:04

7 Answers7

38

Won't it be released automatically?

From the MSDN lock means

System.Threading.Monitor.Enter(x);
try {
   ...
}
finally {
   System.Threading.Monitor.Exit(x);
}

So you don't have to bother.

Mykola Golubyev
  • 57,943
  • 15
  • 89
  • 102
  • Indeed, this would seem correct. You effectively have nested try-finally blocks, which means the two finally blocks *both* get called regardless of what happens. – Noldorin Mar 12 '09 at 16:55
  • 2
    +1, this is the best you can do with CLR 2.0 but it's not actually foolproof. http://blogs.msdn.com/ericlippert/archive/2009/03/06/locks-and-exceptions-do-not-mix.aspx – JaredPar Mar 12 '09 at 19:20
  • Updated link to Eric's article - https://ericlippert.com/2009/03/06/locks-and-exceptions-do-not-mix/ . Please note that while lock itself is no longer leaked, failing with exception from `lock` means update of the state may be half-done defeating the purpose of the lock. Please read the linked article carefully before "don't bother" suggested in this answer. – Alexei Levenkov Feb 25 '21 at 18:02
13

The lock won't be released until you pass out of the scope of the lock(sharedResource.SyncRoot) block. lock (sharedResource.SyncRoot) {} is basically identical to:

Monitor.Enter(sharedResource.SyncRoot);
try
{
}
finally
{
    Monitor.Exit(sharedResource.SyncRoot);
}

You can either do the Enter/Exit yourself if you want more control, or just rescope the lock to what you want, like:

try
{
    lock(sharedResource.SyncRoot)
    {
        int bad = 2 / 0;
    }
}
catch (DivideByZeroException e)
{
   // Lock released by this point.
}
Michael
  • 54,279
  • 5
  • 125
  • 144
  • The first example is good if the resource you are holding is a database and you want to be able to report the error to the database in the Catch, close the Db then release the lock. – shindigo Apr 21 '14 at 14:48
6

Proof.

.method public hidebysig instance void  test(int32 i) cil managed
{
  // Code size       43 (0x2b)
  .maxstack  2
  .locals init ([0] int32 bad,
           [1] class [mscorlib]System.DivideByZeroException e,
           [2] object CS$2$0000)
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldfld      object WebApplication1.myclass::mutex
  IL_0007:  dup
  IL_0008:  stloc.2
  IL_0009:  call       void [mscorlib]System.Threading.Monitor::Enter(object)
  IL_000e:  nop
  .try
  {
    IL_000f:  nop
    .try
    {
      IL_0010:  nop
      IL_0011:  ldc.i4.2
      IL_0012:  ldarg.1
      IL_0013:  div
      IL_0014:  stloc.0
      IL_0015:  nop
      IL_0016:  leave.s    IL_001d
    }  // end .try
    catch [mscorlib]System.DivideByZeroException 
    {
      IL_0018:  stloc.1
      IL_0019:  nop
      IL_001a:  nop
      IL_001b:  leave.s    IL_001d
    }  // end handler
    IL_001d:  nop
    IL_001e:  nop
    IL_001f:  leave.s    IL_0029
  }  // end .try
  finally
  {
    IL_0021:  ldloc.2
    IL_0022:  call       void [mscorlib]System.Threading.Monitor::Exit(object)
    IL_0027:  nop
    IL_0028:  endfinally
  }  // end handler
  IL_0029:  nop
  IL_002a:  ret
} // end of method myclass::test
annakata
  • 74,572
  • 17
  • 113
  • 180
5

Jaredpar posted a link in a comment which I think is worth checking out:

http://blogs.msdn.com/ericlippert/archive/2009/03/06/locks-and-exceptions-do-not-mix.aspx

In this blog post Eric Lippert comments on the problems related to locking in C#:

The problem here is that if the compiler generates a no-op instruction between the monitor enter and the try-protected region then it is possible for the runtime to throw a thread abort exception after the monitor enter but before the try. In that scenario, the finally never runs so the lock leaks, probably eventually deadlocking the program. It would be nice if this were impossible in unoptimized and optimized builds.

Justin Tanner
  • 14,062
  • 17
  • 82
  • 103
3

Your code is perfectly fine. The lock(sth){...} is translated internally to a try finally block.

Grzenio
  • 35,875
  • 47
  • 158
  • 240
1

Wouldnt it just run like so regardless:

try
{
  lock (sharedResource.SyncRoot)
  {
      int bad = 2 / 0;
  }
}
catch (DivideByZeroException e)
{
    // exception caught but lock not released
}
finally
{
      //release lock
}
cgreeno
  • 31,943
  • 7
  • 66
  • 87
1

The lock will be released when the context of it's block is exited, however that happens. In the code example given above the lock will automatically, safely be released as control exits the final } context.

Jeff Kotula
  • 2,114
  • 12
  • 16