This is the twentieth first part of the .NET Inside Out series. For your convenience you can find other parts in the table of contents in Part 1 – Virtual and non-virtual calls in C#
Today we are going to see using
in details across various .NET implementations (.NET Core, .NET Framework and Mono). I explicitly mention platform differences later on. The following code:
1 2 3 |
using(var resource = new Resource()){ // ... } |
is conceptually translated to:
1 2 3 4 5 6 |
var resource = new Resource(); try{ // ... } finally{ if(resource != null) resource.Dispose(); } |
Seems simple enough but it has a lot of gotchas.
Table of Contents
Losing exceptions
Let’s start with the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
using System; using System.Threading.Tasks; public class Program { public static void Main() { using(var resource = new Resource()){ Console.WriteLine("Using"); throw new Exception("Using failed"); } } } public class Resource : IDisposable { public void Dispose(){ Console.WriteLine("Disposing"); throw new Exception("Disposing failed"); } } |
We throw an exception in the try block and while cleaning up the resource (think of it as of an exception while closing a file, for instance). Output:
1 2 3 |
Using Unhandled exception. System.Exception: Using failed at Program.Main() |
So we can see that the resource was not cleaned up at all! Why? That’s because there is no exception handler so in some implementations the finally block is not executed. Let’s try adding it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using System; using System.Threading.Tasks; public class Program { public static void Main() { try{ using(var resource = new Resource()){ Console.WriteLine("Using"); throw new Exception("Using failed"); } }catch(Exception e){ Console.WriteLine("Exception: " + e); } } } public class Resource : IDisposable { public void Dispose(){ Console.WriteLine("Disposing"); throw new Exception("Disposing failed"); } } |
Output:
1 2 3 4 5 |
Using Disposing Exception: System.Exception: Disposing failed at Resource.Dispose() at Program.Main() |
Okay, we see that the Dispose
method was called and threw the exception but we lost the exception from the using
block.
Let’s see if it works in Java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import java.util.*; import java.lang.*; import java.io.*; class Ideone { public static void main (String[] args) throws java.lang.Exception { try(Resource resource = new Resource()){ System.out.println("Trying"); throw new RuntimeException("Trying failed"); } } } class Resource implements Closeable { public void close(){ System.out.println("Closing"); throw new RuntimeException("Closing failed"); } } |
Output:
1 2 3 4 5 6 7 |
Trying Closing Exception in thread "main" java.lang.RuntimeException: Trying failed at Ideone.main(Main.java:11) Suppressed: java.lang.RuntimeException: Closing failed at Resource.close(Main.java:19) at Ideone.main(Main.java:9) |
Okay, try with resources in Java works correctly, prints both exceptions.
So we see C# loses exceptions. Well, let’s continue.
Faulty constructor
What happens when constructor throws?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
using System; using System.Threading.Tasks; public class Program { public static void Main() { using(var resource = new Resource()){ Console.WriteLine("Using"); throw new Exception("Using failed"); } } } public class Resource : IDisposable { public Resource(){ Console.WriteLine("Resource"); throw new Exception("Resource failed"); } public void Dispose(){ Console.WriteLine("Disposing"); throw new Exception("Disposing failed"); } } |
1 2 3 4 |
Resource Unhandled exception. System.Exception: Resource failed at Resource..ctor() at Program.Main() |
So we don’t enter the using
block at all. At least, this is consistent with Java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import java.util.*; import java.lang.*; import java.io.*; class Ideone { public static void main (String[] args) throws java.lang.Exception { try(Resource resource = new Resource()){ System.out.println("Trying"); throw new RuntimeException("Trying failed"); } } } class Resource implements Closeable { public Resource(){ System.out.println("Resource"); throw new RuntimeException("Resource failed"); } public void close(){ System.out.println("Closing"); throw new RuntimeException("Closing failed"); } } |
1 2 3 4 |
Resource Exception in thread "main" java.lang.RuntimeException: Resource failed at Resource.<init>(Main.java:19) at Ideone.main(Main.java:9) |
So we see that even though it looks like the resource is inside using
, it is initialized before entering the try
block. Something similar to old implementation of lock
keyword.
Let’s fix it
There are two places we may get exceptions. One is in the using
block, the other is in the cleanup code. We need to handle both of them, ideally without changing the types. Let’s take this code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
using System; using System.Runtime.ExceptionServices; public class Program { public static void Main() { Exception exception = null; Exception exception2 = null; var resource = new Resource(); try{ Console.WriteLine("Using"); throw new Exception("Using failed"); }catch(Exception e){ exception = e; }finally{ try{ if(resource != null) resource.Dispose(); } catch(Exception e2){ exception2 = e2; } if(exception != null && exception2 != null){ throw new AggregateException(exception, exception2); }else if(exception != null){ ExceptionDispatchInfo.Capture(exception).Throw(); }else if(exception2 != null){ ExceptionDispatchInfo.Capture(exception2).Throw(); } } } } public class Resource : IDisposable { public void Dispose(){ Console.WriteLine("Disposing"); throw new Exception("Disposing failed"); } } |
In liens 8 and 9 we prepare variables for storing exceptions. Next, we enter try
block and do the same things we did previously.
In line 15 we capture the exception thrown, to be used later.
In lines 17-21 we try cleaning up the resource if there is a need.
Finally, in lines 23-28 we decide what to do. If we had two exceptions, we need to throw both of them. So we just use AggregateException
. However, if we had only one of them (line 25 or 27), we rethrow it with preserving the context.
Output with two exceptions:
1 2 3 4 5 6 7 8 9 10 |
Using Disposing Unhandled exception. System.AggregateException: One or more errors occurred. (Using failed) (Disposing failed) ---> System.Exception: Using failed at Program.Main() --- End of inner exception stack trace --- at Program.Main() ---> (Inner Exception #1) System.Exception: Disposing failed at Resource.Dispose() at Program.Main()<--- |
Exception during cleanup only:
1 2 3 4 5 6 7 |
Using Disposing Unhandled exception. System.Exception: Disposing failed at Resource.Dispose() at Program.Main() --- End of stack trace from previous location where exception was thrown --- at Program.Main() |
Exception during using
only:
1 2 3 4 5 6 |
Using Disposing Unhandled exception. System.Exception: Using failed at Program.Main() --- End of stack trace from previous location where exception was thrown --- at Program.Main() |
Looks much better now.
Let’s fix it even more (Mono and .NET Framework)
.NET Core doesn’t allow us, but what if we abort the thread?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
using System; using System.Threading; using System.Runtime.ExceptionServices; public class Program { public static void Main() { var handle1 = new ManualResetEvent(false); var handle2 = new ManualResetEvent(false); var abortedThread = new Thread(() => { try{ Exception exception = null; Exception exception2 = null; var resource = new Resource(); try{ Console.WriteLine("Using"); handle1.Set(); handle2.WaitOne(); // Interruption comes here }catch(Exception e){ exception = e; }finally{ try{ if(resource != null) resource.Dispose(); } catch(Exception e2){ exception2 = e2; } if(exception != null && exception2 != null){ throw new AggregateException(exception, exception2); }else if(exception != null){ ExceptionDispatchInfo.Capture(exception).Throw(); }else if(exception2 != null){ ExceptionDispatchInfo.Capture(exception2).Throw(); } } }catch(Exception e3){ Console.WriteLine("Swallowing everything! " + e3); } Console.WriteLine("This shouldn't appear"); }); abortedThread.Start(); handle1.WaitOne(); abortedThread.Abort(); handle2.Set(); abortedThread.Join(); } } public class Resource : IDisposable { public void Dispose(){ Console.WriteLine("Disposing"); throw new Exception("Disposing failed"); } } |
We create two threads and synchronize them using events. The outer thread tries to kill the inner one, which is in the middle of using
. Output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
Using Disposing Swallowing everything! System.AggregateException: One or more errors occurred. (Thread was being aborted.) (Disposing failed) ---> System.Threading.ThreadAbortException: Thread was being aborted. at Program+<>c__DisplayClass0_0.<Main>b__0 () [0x00038] in <f57b24d7a4d84a82b73f0e7acb34858b>:0 --- End of inner exception stack trace --- at Program+<>c__DisplayClass0_0.<Main>b__0 () [0x0007f] in <f57b24d7a4d84a82b73f0e7acb34858b>:0 ---> (Inner Exception #0) System.Threading.ThreadAbortException: Thread was being aborted. at Program+<>c__DisplayClass0_0.<Main>b__0 () [0x00038] in <f57b24d7a4d84a82b73f0e7acb34858b>:0 <--- ---> (Inner Exception #1) System.Exception: Disposing failed at Resource.Dispose () [0x0000c] in <f57b24d7a4d84a82b73f0e7acb34858b>:0 at Program+<>c__DisplayClass0_0.<Main>b__0 () [0x00048] in <f57b24d7a4d84a82b73f0e7acb34858b>:0 <--- This shouldn't appear |
Looks pretty good. We swallow the exception and print it out, we have (almost) all the details. However, we also have a line This shouldn't appear
. Remember that we cannot stop the thread from aborting just like that, we need to call ResetAbort
. However, in this example we change this behavior. BTW, in this weird tio Mono version, by throwing in finally
you can stop the thread from aborting, as shown here.
Okay, how to fix that? We need to abort the thread again but in some weird version of Mono in tio we cannot do it from its body (we can in .NET Framework). We need to grab another one, as shown here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
using System; using System.Threading; using System.Runtime.ExceptionServices; public class FixingUsing { public static void Main() { var handle1 = new ManualResetEvent(false); var handle2 = new ManualResetEvent(false); var abortedThread = new Thread(() => { try { Exception exception = null; Exception exception2 = null; var resource = new Resource(); try { Console.WriteLine("Using"); handle1.Set(); handle2.WaitOne(); // Interruption comes here } catch (Exception e) { exception = e; } finally { try { if (resource != null) resource.Dispose(); } catch (Exception e2) { exception2 = e2; } var isAborted = Thread.CurrentThread.ThreadState == ThreadState.AbortRequested; Action<object> killMe = (object o) => { Thread.ResetAbort(); if (Type.GetType("Mono.Runtime") != null) { var threadToKill = Thread.CurrentThread; var killer = new Thread(() => { threadToKill.Abort(o); }); killer.Start(); killer.Join(); } else { Thread.CurrentThread.Abort(o); } }; if (exception != null && exception2 != null) { var aggregate = new AggregateException(exception, exception2); if (isAborted) { killMe(aggregate); } else { throw aggregate; } } else if (exception != null) { if (isAborted) { killMe(exception); } else { ExceptionDispatchInfo.Capture(exception).Throw(); } } else if (exception2 != null) { if (isAborted) { killMe(exception); } else { ExceptionDispatchInfo.Capture(exception2).Throw(); } } } } catch (ThreadAbortException e3) { Console.WriteLine("Swallowing everything! " + e3.ExceptionState); } Console.WriteLine("This shouldn't appear"); }); abortedThread.Start(); handle1.WaitOne(); abortedThread.Abort("With old data"); handle2.Set(); abortedThread.Join(); } } public class Resource : IDisposable { public void Dispose() { Console.WriteLine("Disposing"); throw new Exception("Disposing failed"); } } |
Output:
1 2 3 4 5 6 7 8 9 10 11 |
Using Disposing Swallowing everything! System.AggregateException: One or more errors occurred. (Thread was being aborted.) (Disposing failed) ---> System.Threading.ThreadAbortException: Thread was being aborted. at Program+<>c__DisplayClass0_0.<Main>b__0 () [0x00038] in <0bf376f98ae6495e93983a76ab993c03>:0 --- End of inner exception stack trace --- ---> (Inner Exception #0) System.Threading.ThreadAbortException: Thread was being aborted. at Program+<>c__DisplayClass0_0.<Main>b__0 () [0x00038] in <0bf376f98ae6495e93983a76ab993c03>:0 <--- ---> (Inner Exception #1) System.Exception: Disposing failed at Resource.Dispose () [0x0000c] in <0bf376f98ae6495e93983a76ab993c03>:0 at Program+<>c__DisplayClass0_0.<Main>b__0 () [0x0004b] in <0bf376f98ae6495e93983a76ab993c03>:0 <--- |
We miss one last thing, the original value passed to Thread.Abort("With old data")
. But it can be extracted when logging, we just need to cast the exceptions correctly.
This seems to be working a bit better. Just wrap it into a helper method and that’s all.
Bonus chatter: here you can read about doing that in Scala.