Issue
You get CannotUnloadAppDomainException when trying to unload an AppDomain on which a WPF window has been displayed. This is how the exception looks like…
--------------------------- AppDomainUnload --------------------------- Error while unloading appdomain. (Exception from HRESULT: 0x80131015) --------------------------- OK ---------------------------
The callstack associated with this exception is not at all informative. This is all that I have for this exception…
System.CannotUnloadAppDomainException occurred _HResult=-2146234347 _message=Error while unloading appdomain. (Exception from HRESULT: 0x80131015) HResult=-2146234347 IsTransient=false Message=Error while unloading appdomain. (Exception from HRESULT: 0x80131015) Source=mscorlib StackTrace: at System.AppDomain.nUnload(Int32 domainInternal) at System.AppDomain.Unload(AppDomain domain) InnerException:
Repro
This was the code that was shared by the customer which demonstrates the issue. Nothing proprietary hence sharing this out…
Private Sub Button_Click(sender As Object, e As RoutedEventArgs) Handles btn1.Click Try btn1.IsEnabled = False ‘Create a new AppDomain Dim ad As AppDomain = AppDomain.CreateDomain("ad2") Dim exeAssembly As String = [Assembly].GetEntryAssembly().FullName Dim newDomain As NewAppDomain = DirectCast(ad.CreateInstanceAndUnwrap(Assembly.GetEntryAssembly.FullName, GetType(NewAppDomain).FullName), NewAppDomain) 'Call a subroutine to show a WPF window newDomain.ShowWPFWindow() AppDomain.Unload(ad) MsgBox("New AppDomain unloaded successfully") Catch ex As Exception MsgBox(ex.Message) Finally btn1.IsEnabled = True End Try End Sub
So in above code we’re creating an object in a new app domain (“ad2”) and we then call ShowWPFWindow on this new object which will show up the window in a new app domain. Once ShowWPFWindow returns we unload this AppDomain and this where CannotUnloadAppDomainException happens. We do see the application going unresponsive before this CannotUnloadAppDomainException.
Why does a CannotUnloadAppDomainException occur?
The million dollar question is why does this happen? Well from what we’ve found when a WPF Window is showed for the first time on a new app domain a Stylus input thread gets attached to this window for touch input events on touch enabled devices or at least on devices which such components installed.
When AppDomain.Unload call is made this thread fails to shutdown. We do see a thread abort request already sent to this stylus input thread but we don’t see a response yet.
Exception on top of the stack…
0:009> !pe Exception object: 028280c4 Exception type: System.CannotUnloadAppDomainException Message: Error while unloading appdomain. (Exception from HRESULT: 0x80131015) InnerException: <none> StackTrace (generated): SP IP Function 00000000 00000001 mscorlib_ni!System.AppDomain.nUnload(Int32)+0x2 07D3E320 793DCDE9 mscorlib_ni!System.AppDomain.Unload(System.AppDomain)+0x41 StackTraceString: <none> HResult: 80131015 0:009> !threads ThreadCount: 14 UnstartedThread: 0 BackgroundThread: 9 PendingThread: 0 DeadThread: 3 Hosted Runtime: no Lock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 0 1 e10 00b63f40 203a228 Preemptive 025EA018:00000000 00b573b8 0 MTA 2 2 a44 00b65bf0 2b228 Preemptive 0282A108:00000000 00b573b8 0 MTA (Finalizer) XXXX 3 0 00bf2b18 30820 Preemptive 00000000:00000000 00b573b8 0 Ukn XXXX 4 0 00c00a20 39820 Preemptive 00000000:00000000 00b573b8 0 MTA XXXX 5 0 00c09b10 39820 Preemptive 00000000:00000000 00b573b8 0 MTA 5 6 1ec8 00c06d08 10b9228 Preemptive 0280C960:00000000 00b573b8 0 Ukn (Threadpool Worker) 7 7 2388 00c22128 ab028 Preemptive 02600420:00000000 00b573b8 0 MTA 8 8 20e4 00c2b2a8 ab228 Preemptive 025FA3DC:00000000 00b573b8 0 MTA 9 9 22b0 06ff0a58 a7028 Preemptive 0282CAF4:00000000 00b573b8 0 STA System.CannotUnloadAppDomainException 028280c4 11 10 1848 07098f78 2b228 Preemptive 026CE7D8:00000000 00b573b8 0 MTA 16 11 1e74 09e984c8 21228 Preemptive 00000000:00000000 00b573b8 0 Ukn 17 12 22b8 07087b90 102a228 Preemptive 00000000:00000000 00b573b8 0 MTA (Threadpool Worker) 18 13 14f4 0702d878 2b229 Preemptive 027827CC:00000000 09e047a0 0 MTA <<<<--- Stylus input thread 19 14 a6c 07090058 10b9228 Preemptive 00000000:00000000 00b573b8 0 Ukn (Threadpool Worker)
The only thread in that domain is #18, a stylus input thread. A thread abort has already been posted and a debug suspend is pending as well…
0:018> !ThreadState 2b229 Thread Abort Requested <<<- Thread abort requested but yet to shutdown Debug Suspend Pending Legal to Join Background CLR Owns CoInitialized In Multi Threaded Apartment Fully initialized
Please note this is the only thread on this AppDomain now that’s left to exit. Since it fails to exit we get a CannotUnloadAppDomainException.
Workaround for this CannotUnloadAppDomainException?
Only workaround is to move AppDomain+Window creation to a separate thread and then forcefully shutdown the Dispatcher thread (via calls to Dispatcher.CurrentDispatcher.InvokeShutdown()). InvokeShutdown alone can fail unless a GC is forced. Please see below code. I’ve added a new thread worker function called ‘DoWork’ which does the work of creating and displaying the AppDomain window…
‘Thread proc Public Shared Sub DoWork() 'Create a new AppDomain Dim ad As AppDomain = AppDomain.CreateDomain("ad2") Dim exeAssembly As String = [Assembly].GetEntryAssembly().FullName Dim newDomain As NewAppDomain = DirectCast(ad.CreateInstanceAndUnwrap(Assembly.GetEntryAssembly.FullName, GetType(NewAppDomain).FullName), NewAppDomain) 'Call a subroutine to show a WPF window newDomain.ShowWPFWindow() Dispatcher.CurrentDispatcher.InvokeShutdown() <<<--- Workaround GC.Collect() <<<—Workaround AppDomain.Unload(ad) MsgBox("New AppDomain unloaded successfully") End Sub Private Sub Button_Click(sender As Object, e As RoutedEventArgs) Handles btn1.Click ' Try btn1.IsEnabled = False Dim ts As New ThreadStart(AddressOf DoWork) Dim WorkerThread As New System.Threading.Thread(ts) WorkerThread.SetApartmentState(ApartmentState.STA) WorkerThread.Start() WorkerThread.Join() MsgBox("New AppDomain unloaded successfully") Catch ex As Exception MsgBox(ex.Message) Finally btn1.IsEnabled = True End Try End Sub
Please note where we’re calling (workaround) InvokeShutdown.
Dispatcher.CurrentDispatcher.InvokeShutdown() GC.Collect()
GC.Collect is important just to make sure the Pen/Stylus objects are cleared out from memory. Alternatively you can also write above code in a Window’s closing event so that the stylus input thread is detached from this window and the objects associated with it are cleared from memory.