Does Application.Exit() apply only to the AppDomain in which it is called?
Is it the same as in WPF – a single Application object per AppDomain?
Generally speaking, what is the relationship between an Application object
and AppDomain objects?
Regards,
Sunny S.
WPF doesn't change this as WPF is still a .NET application and is still run
by the CLR.
Application.Exit will kill the application you are calling it on and
therefore the AppDomain it is running in.
"Sunny S" <Sun...@discussions.microsoft.com> wrote in message
news:4D3C4E0C-2C92-4015...@microsoft.com...
> Hi,
>
> Does Application.Exit() apply only to the AppDomain in which it is called?
> Is it the same as in WPF - a single Application object per AppDomain?
As far as I'm aware, Application.Exit just stops the UI message loop on
the current thread. It doesn't try to do anything to the AppDomain.
A quick experiment *appears* to show that you can't run a WinForms
message loop on the same thread twice, but I could just be missing
something. If you create a new thread for each one though, you can call
Application.Exit in the same AppDomain multiple times perfectly
reasonably:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
public class Test
{
static void Main()
{
for (int i=0; i < 5; i++)
{
Thread t = new Thread(ShowUI);
t.SetApartmentState(ApartmentState.STA);
t.Start(i);
t.Join();
}
}
static void ShowUI(object state)
{
Form form = new Form { Size = new Size(200, 200),
Text = "Form #"+state };
Button b = new Button { Text = "Die!",
Location = new Point(5, 5) };
b.Click += (s, e) => { Application.Exit(); };
form.Controls.Add(b);
Application.Run(form);
}
}
--
Jon Skeet - <sk...@pobox.com>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
World class .NET training in the UK: http://iterativetraining.co.uk
Application.Exit() is a Forms API, WPF does not run a message loop so A.E
makes no sense in WPF.
Willy.
> Application.Exit() is a Forms API,
That is true.
> so A.E makes no sense in WPF.
That may be strictly true, but try this on for size:
---8<---
using System;
using System.Windows;
using System.Threading;
using System.Runtime.InteropServices;
using System.Windows.Controls;
public class App
{
[DllImport("user32.dll")]
static extern void PostQuitMessage(int nExitCode);
static Button MakeQuitButton()
{
Button result = new Button();
result.Content = "Quit";
result.Click += (s,e) => PostQuitMessage(0);
return result;
}
[STAThread]
static void Main()
{
new Application().Run(new Window()
{
Content = MakeQuitButton()
});
}
}
--->8---
> WPF does not run a message loop
This is empirically false:
---8<---
ntkrnlpa.exe!KiSwapContext+0x2f
ntkrnlpa.exe!KiSwapThread+0x8a
ntkrnlpa.exe!KeWaitForSingleObject+0x1c2
ntkrnlpa.exe!KiSuspendThread+0x18
ntkrnlpa.exe!KiDeliverApc+0x124
ntkrnlpa.exe!KiSwapThread+0xa8
ntkrnlpa.exe!KeWaitForSingleObject+0x1c2
win32k.sys!xxxSleepThread+0x192
win32k.sys!xxxRealInternalGetMessage+0x418
win32k.sys!NtUserGetMessage+0x27
ntkrnlpa.exe!KiFastCallEntry+0xfc
ntdll.dll!KiFastSystemCallRet
USER32.dll!NtUserGetMessage+0xc
WindowsBase.ni.dll+0x5e623
WindowsBase.ni.dll!ResetValue+0xd
WindowsBase.ni.dll!get_SourceProperty+0x1
WindowsBase.ni.dll!.ctor+0xe
WindowsBase.ni.dll!ConvertToString+0x21
WindowsBase.ni.dll!ConvertToString+0x2
PresentationFramework.ni.dll!AlignContent+0x7b
mscorwks.dll!CallDescrWorkerWithHandler+0xa3
mscorwks.dll!MethodDesc::CallDescr+0x19c
mscorwks.dll!MethodDesc::CallTargetWorker+0x1f
mscorwks.dll!MethodDescCallSite::Call_RetArgSlot+0x18
mscorwks.dll!ClassLoader::RunMain+0x263
mscorwks.dll!Assembly::ExecuteMainMethod+0xa6
mscorwks.dll!SystemDomain::ExecuteMainMethod+0x43f
mscorwks.dll!ExecuteEXE+0x59
mscorwks.dll!_CorExeMain+0x15c
mscoree.dll!_CorExeMain+0x2c
KERNEL32.dll!BaseProcessStart+0x23
--->8---
NtUserGetMessage is there, clear as day.
-- Barry
Well here you are calling PostQuitMessage which doesn't really do what it's
supposed to do, also System.Windows.Forms.Application.Exit doesn't do what
it has to do here (in a WPF based app.). Take a look at the messages using
Spyxx.exe, you'll see that none of the API's are sending WM_QUIT message to
your application. A.E does not terminate the UI thread whereas
PostQuitMessage simply terminates the UI thread and as such the application.
If you are looking for the equivalent of A.E in WPF, then take a look at
Application.Shutdown, PostQuitMessage shouldn't be used at all.
>> WPF does not run a message loop
>
> This is empirically false:
>
Well, above is quite confusing, I should have been more explicit.
Every Windows Application, no matter what the UI framework used , will have
at least one top level window, this is also true for WPF based application.
A minimal WPF application has at least one single Window (HWND) that is used
as a *host* or wrap WPF.
This top level Window, with it's associated message loop and WndProc,
receives/handles all Windows messages for the application, WPF itself is not
HWND based, Windows messages are translated (when appropriate) into CLR
events that get routed to the the WPF Visuals. This top level HWND isn't
exposed and accessible like it is in other frameworks like Windows.Forms,
you can't override WndProc for instance,you shouldn't even assume such
Window is present. Note also that the WPF core registers Windows messages
that aren't know to Windows.Forms and vice-versa, that's why there is an
WPF/Forms interop layer in the form of the HwndHost control.
Willy.
> "Barry Kelly" <barry....@gmail.com> wrote:
>
> > Willy Denoyette [MVP] wrote:
> >
> >> Application.Exit() is a Forms API,
> >
> > That is true.
> Well here you are calling PostQuitMessage which doesn't really do what it's
> supposed to do
PostQuitMessage posts WM_QUIT to the current thread's message queue.
This is no different in a WPF application than in any other Windows
application. WM_QUIT causes GetMessage to return 0.
WindowsBase.dll, System.Windows.Threading.Dispatcher::PushFrameImpl()
contains the message loop which calls GetMessage. It exits the loop when
GetMessage returns 0.
It's a pretty simple stack from there on out to exit the application,
provided you don't have extra code after the Application.Run() call.
---8<---
|> 0:000> !clrstack
|> OS Thread Id: 0x1c68 (0)
|> ESP EIP
|> 0012f358 56d8e623 MS.Win32.UnsafeNativeMethods.GetMessageW(System.Windows.Interop.MSG ByRef, System.Runtime.InteropServices.HandleRef, Int32, Int32)
|> 0012f36c 56d6197e System.Windows.Threading.Dispatcher.GetMessage(System.Windows.Interop.MSG ByRef, IntPtr, Int32, Int32)
|> 0012f3b8 56d617e3 System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame)
|> 0012f408 56d616c7 System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame)
|> 0012f424 56d6162d System.Windows.Threading.Dispatcher.Run()
|> 0012f430 5533dce0 System.Windows.Application.RunInternal(System.Windows.Window)
|> 0012f45c 5533db15 System.Windows.Application.Run(System.Windows.Window)
|> 0012f46c 00c5010d App.Main()
--->8---
>, also System.Windows.Forms.Application.Exit doesn't do what
> it has to do here (in a WPF based app.).
I never said it did.
> Take a look at the messages using
> Spyxx.exe, you'll see that none of the API's are sending WM_QUIT message to
> your application.
Did you look at the window messages or the thread messages? WM_QUIT
isn't normally sent to the windows, just the thread message queue.
SpyXX on my system crashes when it tries to hook WPF thread messages,
but WinDbg shows the thread message queue calls just fine (as indicated
above).
> PostQuitMessage simply terminates the UI thread and as such the application.
No it doesn't.
It posts a message to the thread message queue and then returns to its
caller immediately.
This can be verified in the debugger, or simply by adding code to the
appropriate places in a sample application:
---8<---
using System;
using System.Windows;
using System.Threading;
using System.Runtime.InteropServices;
using System.Windows.Controls;
public class App
{
[DllImport("user32.dll")]
static extern void PostQuitMessage(int nExitCode);
static Button MakeQuitCButton()
{
Button result = new Button();
result.Content = "Quit";
result.Click += (s,e) =>
{
Console.WriteLine("Before Quit message");
PostQuitMessage(0);
Console.WriteLine("After Quit message");
};
return result;
}
[STAThread]
static void Main()
{
Console.WriteLine("Before Application.Run");
new Application().Run(new Window()
{
Content = MakeQuitCButton()
});
Console.WriteLine("After Application.Run");
}
}
--->8---
This shows the following on my machine, after I press the Quit button:
---8<---
Before Application.Run
Before Quit message
After Quit message
After Application.Run
--->8---
> If you are looking for the equivalent of A.E in WPF, then take a look at
> Application.Shutdown, PostQuitMessage shouldn't be used at all.
I agree with you, one should use Application.Shutdown. You end up
missing out a whole load of app teardown logic if you use
PostQuitMessage.
> >> WPF does not run a message loop
> >
> > This is empirically false:
>
> Well, above is quite confusing, I should have been more explicit.
> Every Windows Application, no matter what the UI framework used , will have
> at least one top level window, this is also true for WPF based application.
> A minimal WPF application has at least one single Window (HWND) that is used
> as a *host* or wrap WPF.
I never asserted that WPF was based on windows all the way down to each
control - that would limit its functionality and performance quite
severely, and you wouldn't get all the scene graph possibilities with
transforms, composing in accelerated hardware etc, without undesirable
clipping. Not even non-WPF applications use windows all the way down,
including IE, Firefox, etc. See also e.g. TGraphicControl in Delphi
(mouse-only input), etc. WPF is in no way new or original in this
respect.
You did, however, appear to assert that WPF applications don't have a
message loop, which is nonsense.
> This top level Window, with it's associated message loop and WndProc,
> receives/handles all Windows messages for the application,
There will be at least as many HWNDs as there are separate top-level
windows for that application on the desktop, each one with an
appropriate WNDPROC.
Well, It doesn't post a WM_QUIT message to the UI's thread queue when run on
Vista (both RTM and SP1), of course it does post WM_QUIT when called in a
Windows.Forms based application (or any non managed windows app.).
I know that.
> SpyXX on my system crashes when it tries to hook WPF thread messages,
> but WinDbg shows the thread message queue calls just fine (as indicated
> above).
>
SpyXX has some troubles to enumerate "managed" processes when run on 32bit,
and may fail to inject the hook into a managed process on Vista32. The only
combination I found working reliably is: spyxx.exe 64-bit on Vista64 running
with debug privileges. Beware that once SpyXX has failed to run, it will
probably fail until after reboot.
>> PostQuitMessage simply terminates the UI thread and as such the
>> application.
>
> No it doesn't.
>
It terminates the application without posting a WM_QUIT message on Vista,
instead it sends a private registered message (amongs a bunch of others) to
the dispatcher which finally terminates the session and returns from Run.
> It posts a message to the thread message queue and then returns to its
> caller immediately.
>
It doesn't post a WM_QUIT message on Vista. This is different from Windows
Forms which posts a WM_QUIT message (amongs other messages to the UI
thread's queue).
Oh, but I never said that the Click handler wasn't called! I only said that
there was no WM_QUIT message posted, sure I should have mentioned "when run
on VISTA!"
Note also that the CLR stack looks somewhat different when run on Vista-64,
the unmanaged stack is also quite different from what you get on Vista32 and
XP.
OS Thread Id: 0x1af4 (0)
Child-SP RetAddr Call Site
000000000064e520 000006422da9f1c1
DomainBoundILStubClass.IL_STUB(System.Windows.Interop.MSG ByRef, Int32,
Int32, Int32, Boolean ByRef)
000000000064e6e0 000006422da9efef
System.Windows.Threading.Dispatcher.GetMessage(System.Windows.Interop.MSG
ByRef, IntPtr, Int32, Int32)
000000000064e790 000006422da9eefe
System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame)
000000000064e860 000006422a5cc061
System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame)
000000000064e8c0 000006422a5cbd4d
System.Windows.Application.RunInternal(System.Windows.Window)
000000000064e930 0000064280150187
System.Windows.Application.Run(System.Windows.Window)
000000000064e980 000006427f67ba32 App.Main()
Unmanaged stack..
0:000> kb
RetAddr : Args to Child
: Call Site
00000000`772dd0ce : 0000988e`36afa07e 00000000`006c8bc0 00000000`00000000
00000000`00000000 : USER32!ZwUserGetMessage+0xa
000007fe`fde8589e : 00000000`006c8bc0 00000000`00000001 00000000`00000000
00000642`7f88d142 : USER32!GetMessageW+0x34
00000642`7f679b67 : 00000000`0051ebc0 00000000`006c8bc0 0000988e`37354ec0
00000642`7f3d6ac0 : MSCTF!CThreadInputMgr::GetMessageW+0x42
*** WARNING: Unable to verify checksum for
C:\Windows\assembly\NativeImages_v2.0.50727_64\WindowsBase\3fd6671f329e84b22e22624974ade4a3\WindowsBase.ni.dll
00000642`2da9fc23 : 00000000`1b473cc0 00000000`006c8bc0 00000000`006c8bc0
00000642`2dd2a780 : mscorwks!DoCLRToCOMCall+0x177
00000642`2da9f1c1 : 00000000`0051f190 00000000`0051e9c1 00000000`0051eab8
00000000`0051ebc0 : WindowsBase_ni!GetLocalizedFailureCodeMessage+0xad
[d:\SP1\windows\wcp\Base\MS\Internal\Security\RightsManagement\Errors.cs @
93]
00000642`2da9efef : 00000000`02db7318 00000000`0051ebc0 00000000`00000000
00000000`00000000 : WindowsBase_ni!GetContentIdFromPublishLicense+0x3
[d:\SP1\windows\wcp\Base\MS\Internal\Security\RightsManagement\ClientSession.cs
@ 1762]
00000642`2da9eefe : 00000000`02da5a50 00000000`0051eca0 00000000`02d86ee8
00000000`02db9058 : WindowsBase_ni!GetContentIdFromLicense+0x76
[d:\SP1\windows\wcp\Base\MS\Internal\Security\RightsManagement\ClientSession.cs
@ 1489]
*** WARNING: Unable to verify checksum for
C:\Windows\assembly\NativeImages_v2.0.50727_64\PresentationFramewo#\0faafa8d4d1c47956dbdb97aaebc9715\PresentationFramework.ni.dll
00000642`2a5cc061 : 00000000`02e660c8 00000000`02db7318 00000000`02db7214
feeefeee`feeefeee :
WindowsBase_ni!ExtractApplicationSpecificDataFromLicense+0x18
[d:\SP1\windows\wcp\Base\MS\Internal\Security\RightsManagement\ClientSession.cs
@ 1407]
00000642`2a5cbd4d : 00000000`02d86ee8 00000000`02db9058 00000000`0000000a
00000000`00000014 : PresentationFramework_ni!get_ChildTextView+0x22
[d:\SP1\windows\wcp\Framework\System\Windows\Documents\documentsequencetextview.cs
@ 610]
I said *WPF* did not have a message loop, I didn't say *WPF applications*,
but as sa id before, I should have been more explicit.
WPF doesn't actually knows anything about Windows messages and queues, it's
the host (the top level frame (a HwndWrapper)) that provides the message
queue and the wndproc not the framework (WPF) .
>> This top level Window, with it's associated message loop and WndProc,
>> receives/handles all Windows messages for the application,
>
> There will be at least as many HWNDs as there are separate top-level
> windows for that application on the desktop, each one with an
> appropriate WNDPROC.
>
Yep, there are"visible" HWND's for the application frame , the menu's and
some other stuff (all top-level windows) plus a bunch of hidden Windows (the
# of them depending on the OS version), that's why I said at least one HWND
per application.
Willy.
Barry has this one exactly right. PostQuitMessage is a Win32 function, it
couldn't care less whether your app is WPF or WinForms or MFC or whatever.
It posts WM_QUIT and returns.
I don't know how you can get any clearer than this:
http://msdn2.microsoft.com/en-us/library/ms644945(VS.85).aspx
"The PostQuitMessage function posts a WM_QUIT message to the thread's
message queue and returns immediately; the function simply indicates to the
system that the thread is requesting to quit at some time in the future."
The docs are clear, but I'm afraid they are missing some additional remarks
when run on Vista (don't know what happens on XP SP2 and SP3).
Please don't point to the docs to prove I'm wrong, use some debugging tools
and correct me if you notice the *documented* behavior.
Running SpyXX [1] doesn't reveal any WM_QUIT message for a WPF enabled
application (say the sample posted by Barry), it does (as expected) when
called from any other type of application (managed Forms, and native MFC and
Win32).
If you can't get Spyxx (from VS2008) to work for you, or if you don't fully
trust what SPxx reveals(like I do), you can always run the app in the
debugger (say Windbg), set a breakpoint on GetMessageW and inspect the MSG
struct returned (there are quite a lot of them) when you call the API, you
won't see any WM_QUIT message (at least I don't).
Willy.
On Vista32 SP1 and Vista64 SP1 and WS2008, all running the same v3.5
Framework bits.
You really should break on PostMessage and PostThreadMessage (A and W
variants), because it's entirely possible that PostQuitMessage did exactly
what the documentation described, but the .NET method (Application.Shutdown
I guess) triggered ending the message loop so GetMessage was never called.
For example: put this code in any of the application types where you do see
WM_QUIT:
PostQuitMessage();
ExitProcess();
Clearly the behavior of PostQuitMessage hasn't changed, yet you'll never see
WM_QUIT returned by GetMessage.
Also, I did break on PostQuitMessage (there are no A or W variants) don't
worry, your assumption that PQM has not changed may be valid for the "user"
portion, which is a small thunk [1] that calls into the kernel (syscall),
that's the place where messages are constructed, and that's where things got
changed dramatically in Vista, that's the portion that knows about "Avalon"
and the DWM. That's the portion that needs to coordinate with the DWM
composition, that's the part that handles PQM when called from a WPF
application running on Vista with DWM (glass) enabled.
[1]
0:000> uf user32!postquitmessage
USER32!PostQuitMessage:
00000000`76e90918 4863c9 movsxd rcx,ecx
00000000`76e9091b ba34000000 mov edx,34h
00000000`76e90920 e94bcc0000 jmp USER32!ZwUserCallOneParam
(00000000`76e9d570)
USER32!ZwUserCallOneParam:
00000000`76e9d570 4c8bd1 mov r10,rcx
00000000`76e9d573 b802100000 mov eax,1002h
00000000`76e9d578 0f05 syscall
00000000`76e9d57a c3 ret
Once again, the message loop is not terminated as a result of a WM_QUIT
message put in the message queue, none of the GetMessage calls return 0 once
PQM has been called, that means none of them have "picked-up" a WM_QUIT.
It's the DWM that stops the message loop, which doesn't mean that PQM is the
right API to call from WPF (it isn't).
You should start to convince yourself, I can't post more the debugger output
here. Start a kernel debugger session and look what PQM does in the kernel,
do the same thing when calling Application.Shutdow, look how thing got
coordinated with the DWM (using ALPC's ) over you will understand what I
mean here.
Don't be surprised when you got blocked by DRM in kernel debugger, don't be
surprised to see some COM interaction between the processes and don't be
surprised to find a some undocumented Windows messages in the application
queue.
Willy.
Gentlemen, thank you very much for an interesting discussion, from which I
learned a lot, but I feel I had to be more specific.
1. Let’s say I’ve started a SimpleWinForm application. I presume that means
starting a message loop.
2. From within that application I create an AppDomain in which I create a
new thread on which I call Application.Run(new SomeOtherWinForm()). See, for
example, Jon Skeet’s code above except that the code should be running on a
dedicated thread in a separate AppDomain.
Microsoft documentation says that Application.Exit() method ‘Informs ALL
message pumps that they must terminate, and then closes all application
windows after the messages have been processed’.
From the above I would conclude that upon calling Application.Exit() both
SomeOtherWinForm and SimpleWinForm should close and the whole thing should
terminate. Experiments show that this is not the case – only SomeOtherWinForm
closes.
Again, Microsoft documentation for System.Windows.Application (not
System.Windows.Forms.Application!) says that there is only one instance of
Application object per AppDomain. It looks as if the same relationship is
true for System.Windows.Forms.Application objects and AppDomains, because
this would explain the above behaviour, but I wasn’t sure.
SS
When the documentation says "all running message loops on all threads and
closes all windows of the application." it really should say in the callers
AppDomain (AD) and it's owning AD's.
That means, that if you call Application.Exit in a child AD , only the child
AD windows will be affected. If, however you cal A.E from the parent AD ,
then also the child AD windows will be affected.
Note also that Windows.Forms was not designed to be run from within multiple
domains in a single process.
Willy.
Thanks very much for a quick reply. Can you see any immediate problems I am
going to run into if I use Windows.Forms in multiple AppDomains?
SS
I modified your example code to create two threads and to start message
loops on each of them as follows:
static void Main()
{
{
Thread t = new Thread(ShowUI1);
t.SetApartmentState(ApartmentState.STA);
t.Start();
Thread t2 = new Thread(ShowUI2);
t2.SetApartmentState(ApartmentState.STA);
t2.Start();
}
}
static void ShowUI1()
{
Form form = new Form
{
Size = new Size(200, 200),
Text = "Form UI1"
};
Button b = new Button
{
Text = "Die!",
Location = new Point(5, 5)
};
b.Click += (s, e) => { Application.Exit(); };
form.Controls.Add(b);
Application.Run(form);
}
static void ShowUI2()
{
Form form = new Form
{
Size = new Size(200, 200),
Text = "Form UI2"
};
Button b = new Button
{
Text = "Die!",
Location = new Point(5, 5)
};
b.Click += (s, e) => { Application.Exit(); };
form.Controls.Add(b);
Application.Run(form);
}
If you run this code, you’ll see that when you kill one of the Forms the
other dies too and the application shuts down. Apparently, calling
Application.Exit() on one thread kills the other thread's UI as well. This is
in line with Microsoft documentation saying that Application.Exit() method
‘Informs all message pumps that they must terminate, and then closes all
application windows after the messages have been processed’.
Things seem to be different if you create these two threads in different
AppDomains like so:
public class Test: MarshalByRefObject
{
static void Main()
{
{
AppDomain ad1 = AppDomain.CreateDomain("AD1");
Test t1 = (Test)
ad1.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, "Test");
t1.StartUI1();
AppDomain ad2 = AppDomain.CreateDomain("AD2");
Test t2 =
(Test)ad2.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName,
"Test");
t2.StartUI2();
}
}
public void StartUI1()
{
Thread t = new Thread(ShowUI1);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
public void StartUI2()
{
Thread t = new Thread(ShowUI2);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
public void ShowUI1()
{
Form form = new Form
{
Size = new Size(200, 200),
Text = "Form UI1"
};
Button b = new Button
{
Text = "Die!",
Location = new Point(5, 5)
};
b.Click += (s, e) => { Application.Exit(); };
form.Controls.Add(b);
Application.Run(form);
}
public void ShowUI2()
{
Form form = new Form
{
Size = new Size(200, 200),
Text = "Form UI2"
};
Button b = new Button
{
Text = "Die!",
Location = new Point(5, 5)
};
b.Click += (s, e) => { Application.Exit(); };
form.Controls.Add(b);
Application.Run(form);
}
}
In this case killing one of the Forms doesn’t kill the other one and this is
not in line with the documentation.
That’s, essentially, what my question was about.
Thanks for replying, it’s nice to hear from you again:)
SS
I’ve just done some more tests. My understanding of Willy Denoyette’s
explanation (see at the bottom of this thread) was that Application.Exit()
kills the message pumps in the current Appdomain and its ‘child’ AppDomains.
To verify this I further modified the code with AppDomains so that one of
the threads creates yet another ‘child’ AppDomain and starts yet another
message loop in it. This test showed that my understanding was incorrect. All
three message loops in those AppDomains are independent in the sense that
killing one of them doesn’t kill the others.
Again, thanks for the reply. You might be interested in reading (and,
perhaps, commenting on:)) my reply to Jon Skeet above. I've done some
experiments to clarify dependecies between message pumps running in different
AppDomains with regards to Application.Exit()
I know this is quite confusing, but I'm not sure why you consider this "not
inline with the docs".
Note that the docs talks about applications, and stopping all message pumps
in the application, it doesn't talk about threads running other AD's.
In your last sample you are creating separate AD/thread pairs *and* you
create a new application (Application.Run(..) in each of the
domains/threads, calling A.E in one of the AD/threads only stops the
(single) message pumps in this application, which is inline with the docs,
right?
Now, say the from AD1/thread1 you create another thread, and you create a
window and run a message pump in this thread. That means, you have
AD1/thread1 and thread2, and two separate message pumps. Calling A.E from
one of these threads will stop all (2) pumps, which is inline with the docs,
right?
And, say you have a AD/thread that runs an application
(Application.Run(...)], and this thread creates another AD that shows a
form. Here you have two AD's, one *Application* and only one thread that
pumps the message queue of both the main form and the other AD's form. When
you call A.E from the second form code, you will simply unload the second AD
and it's form, but you won't stop the message pump. Calling A.E from the
main form will stop the message pump and close all forms tear down the AD's
for the whole application, which is again inline with the doc's.
Willy.
I agree that 'not in line with the docs' was, perhaps, an overstatement:).
What I actually wanted to say is that as far as I am concerned the
documentation was not clear about the scope of the Application object and the
relationship between Application, A.E method and AppDomains.
For example, you say ' ... you create a new application (Application.Run(..)
in each of the domains/threads ...' Define 'application'. Surely it's not a
new Application object or is it? BTW, the documentation just says that
calling A.R 'Begins running a standard application message loop on the
current thread'. I am not trying to be argumentative, I'm just trying to
understand what's going on.
Let's take one of your scenarios: "From AD1/thread1 you create another
thread, and you create a window and run a message pump in this thread." Why
can't I use the same logic as above and say that by 'creating a window and
running a message pump' on a new thread (through Application.Run(Form), I
presume) I create a new application?
Let's futher modify this scenario so that the second thread is created, as
above, from AD1/thread1, but in a separate AppDomain (AD2), so that AD2 is a
'child' of AD1. In this case, calling A.E on either of the threads doesn't
stop the other (I did test it:).
You see, this is the sort of questions I was trying to clarify for myself
and I found your comments quite helpful, thank you.
WIlly.
"Sunny S" <sun...@newsgroup.nospam> wrote in message
news:5740021E-1C8F-422F...@microsoft.com...
> Hi Willi,
>
> I agree that 'not in line with the docs' was, perhaps, an overstatement:).
> What I actually wanted to say is that as far as I am concerned the
> documentation was not clear about the scope of the Application object and
> the
> relationship between Application, A.E method and AppDomains.
>
Well, as said before, Windows.Forms was designed to be used from the
"default domain" only and not from muttiple AD's running in the same
process. The reason is quite simple, the underlying Win32 Windows
architecture doesn't know about AD's, it only knows about processes and
threads and one thread which is important in the "Windows" context is the UI
thread. It's the thread that must run a message pump in order to retrieve
Window messages from the application and from the System.
> For example, you say ' ... you create a new application
> (Application.Run(..)
> in each of the domains/threads ...' Define 'application'. Surely it's not
> a
> new Application object or is it? BTW, the documentation just says that
> calling A.R 'Begins running a standard application message loop on the
> current thread'. I am not trying to be argumentative, I'm just trying to
> understand what's going on.
>
A.R creates an ApplicationContext (and a static Application instance ), it's
the ApplicationContext who actually defines *the* application in terms of
the MSDN docs.
> Let's take one of your scenarios: "From AD1/thread1 you create another
> thread, and you create a window and run a message pump in this thread."
> Why
> can't I use the same logic as above and say that by 'creating a window and
> running a message pump' on a new thread (through Application.Run(Form), I
> presume) I create a new application?
You create a new Application in terms of the docs, that's right.
>
> Let's futher modify this scenario so that the second thread is created, as
> above, from AD1/thread1, but in a separate AppDomain (AD2), so that AD2 is
> a
> 'child' of AD1. In this case, calling A.E on either of the threads doesn't
> stop the other (I did test it:).
>
Ok so you have:
AD1/T1 -> creates T2 -> creates AD2 -> call A.R which creates a new
ApplicationContext (a new application as per msdn) which start a pump on T2.
Calling A.E in T2 will stop the pump on T2, destroy the ApplicationContext
and as such stop this application.
The fact that you do this is a new AD is irrelevant, an AD != an
"application".
We are almost there:):):)
To recap, we have: AD1/T1 -> calls A.R(Form1); creates T2 -> creates AD2 ->
call A.R(Form2). Here calling A.E on T2 does close Form2, but doesn't close
Form1. The reverse is also true: calling A.E on T1 closes Form1, but doesn't
close Form2.
I accept your explanation that A.R(Form2) creates a new 'application' that
is independent of the application running Form1, so exiting one of them
doesn't affect the other.
Why then things are different for the following scenario: AD1/T1 -> calls
A.R(Form1); creates T2 -> calls A.R(Form2), where we don't create a separate
AppDomain for T2? As you pointed out erlier, closing either Form1 or Form2
closes the other Form and shuts down the application.
Apparently, calling A.R on a different thread in a separate AppDomain is
somehow different from calling it in the same AppDomain. Would it be correct
to assume that the scope of Application.Exit() is limited to the current
AppDomain only?
Yep.
> I accept your explanation that A.R(Form2) creates a new 'application'
> that
> is independent of the application running Form1, so exiting one of them
> doesn't affect the other.
>
> Why then things are different for the following scenario: AD1/T1 -> calls
> A.R(Form1); creates T2 -> calls A.R(Form2), where we don't create a
> separate
> AppDomain for T2? As you pointed out erlier, closing either Form1 or Form2
> closes the other Form and shuts down the application.
>
Here you create one AD, 2 threads, 2 applications (ApplicationContexts,
ApplicationThreadContexts, etc... ) , 2 message pumps (one per thread).
When you call A.E in one of the threads, you get rid of all of the
ApllicationContexts in the containing AD and it's AD children.
Note that what's happening is quite more complex, but that's the big
picture.
Why are you so interested in A.E's behavior, which is not the most
appropriate way to stop an application anyway, just currious.
Well, what can I say? A big thank you, again:)
As for your question why am I so interested in A.E -- at this point it's
already, perhaps, a bit of an obsession. My original interest was triggered
by attempts to find a way to rudely shut down an application that is running
in an AppDomain that was created by a host. The app is supposed to be written
by a third party, should be considered as an untrusted one and peppered with
bugs (that is, in addition to our own bugs:)). If that app enters a tight
loop, then I couldn’t see a way to quit it gracefully other than calling A.E.
Unloading an AppDomain, even if that was possible with a foreground thread
still running in it, wouldn’t be a good option since we would loose state
information. So A.E seemed to be a reasonable option. So I started thinking
of what would happen if I inject a thread into that problematic AppDomain and
call A.E on it, and then I came up with some other scenarios most of which
included A.E in one way or another. Obviously, I needed to understand what
are possible ramifications, etc. BTW, where and how can I learn what happens
‘under the hood’? I tried Reflector, but the most interesting (and messy?)
parts of functionality are hidden in internal methods which Reflector
wouldn’t ‘reflect’:)). I don't want to be too intrusive, but how did you
learn what's going on behind 'the big picture'?
Regards,
SS
Sorry for the late response to you, I am out of office yesterday. Anyway, I
am glad Willy has provided a great and informative discussion with you.
I assume your application is coded in Winform since you are using
System.Winforms.Forms.Application. So, your application created a seperate
AppDomain to host the 3rd party code for security/robustness reason ? Can
you tell us if the 3rd party code is also GUI code? This design and
achitecture looks strange to me, since I did not see any programs using this
design. We normally run the semi-trusted non-GUI addin in the separate
AppDomain.
I ask these because Application.Exit can only be used in GUI Winform code
not normal non-GUI code. Also, it can only shut down the GUI threads in the
*Application*. If the 3rd party code created a few more worker(non-GUI)
threads, they will be cleaned by Application.Exit. As Willy originally
pointed out, Application.Exit() has nothing to do with AppDomain, so it will
not help to clearn AppDomain. Why don't you call AppDomain.Unload method to
get rid of the buggy 3rd party code?
Regarding your last question, I think Reflector should be the best tool to
understand the .Net BCL internal work. What internal call functions do you
have problem to understand? As I know Winform purely encapsulates the Win32
GUI code, so I assume most of the non-.Net code is p/invoking the Win32
User32 APIs. The Win32 GUI programming books(such as <Programming Windows>)
should be good resource to help you understand GUI APIs.
Hope this helps.
Best regards,
Jeffrey Tan
Microsoft Online Community Support
=========================================
Delighting our customers is our #1 priority. We welcome your comments and
suggestions about how we can improve the support we provide to you. Please
feel free to let my manager know what you think of the level of service
provided. You can send feedback directly to my manager at:
msd...@microsoft.com.
This posting is provided "AS IS" with no warranties, and confers no rights.
Thanks for replying and for your comments. To answer your questions,
1. Yes, the app we are writing is being coded in Winform, it's not a Web
application, at least for the time being.
2. I can see two explanations for our design being strange to you:) - a) we
are doing something genuinely stupid; b) we are doing something genuinely new
(a combination of both is also an option:):):)). On a serious note, we are
trying to create an app that would host some other applications, either GUI
or non-GUI, which would be configurable through the host UI. The host is
supposed to be non-intrusive; once the apps are configured they should run as
there is no host, although they would be required to respond to host’s
request about their status.
3. Why don’t I call AppDomain.Unload()? I actually do, but there are
scenarios when I don’t want or cannot do this. (a) If the 3rd party app that
sits in this AppDomain is running a foreground thread then AppDomain.Unload
throws an exception. (b) If there is a problem with a 3rd party app, so that
it cannot continue, we would like to stop it - not in a graceful way, perhaps
- and grab some status data. Then we can unload the whole thing. (c) We
should be able to stop the 3rd party app, reconfigure it and start again.
4. Just to illustrate what I was talking about when I said that there were
limits to which Reflector could ‘reflect’, let’s take the Object class. Look,
for example, at
internal static extern bool InternalEquals(object objA, object objB)
As far as I know, there is no matching Win32 API to p/invoke and this is as
far as I can go in exploring the CLR internals.
Anyway, thank you very much for your help.
SS
Thanks for your feedback.
Regarding "(a) If the 3rd party app that sits in this AppDomain is running
a foreground thread then AppDomain.Unload throws an exception", do you mean
ThreadAbortException thrown in the thread in the 3rd party AppDomain? Yes,
AppDomain.Unload() method uses Thread.Abort() method on the target thread
to terminate the threads. ThreadAbortException will perform stack unwind on
the target stack to perform clean-up and then terminate the target thread.
Then, what is the problem of this behavior?
Regarding (b) and (c), I also do not think I understand the key points.
What is the problem of AppDomain.Unload() against your (b) and (c)
requirement?
Regarding the Reflector issue, it seems that you are interested in the
internal routines implemented by the CLR. Yes, a lot of .Net services are
implemented in the CLR via native code. They are using "FCall" into the CLR
which can not be examined using Reflector. The only way to examine its
implementation is reversing engineer(disassemble) it. Also, Microsoft
released an open-source CLR implementation called SSCLI, which contains an
example implementation of CLR and .Net Framework. You may examine its
source code to understand these FCall functions. The SSCLI source code is
not guaranteed to be the same as CLR, but most of the principles are the
same. The articles below also talks about FCall:
http://blogs.msdn.com/yunjin/archive/2004/02/08/69906.aspx
https://blogs.msdn.com/joelpob/archive/2003/12/18/53745.aspx
Also, a lot of interesting books talk about CLR internal implementation,
such as Jeffrey Richter's <CLR via C#>, Don Box's <Essential .Net>, <Shared
Source CLI Essentials> etc.. They are helpful to understand CLR principles.
Thanks for replying and my special thanks for the link to FCall. This should
be very useful (or at least very interesting:) ).
Regarding (a) in my previous post: The exception that AppDomain throws is
actually CannotUnloadAppDomainException and VS2008 gives a hint: 'Make sure
you are not trying to unload an application domain that ... has a running
thread ...'. Again, this is only a problem for foreground threads.
Regarding (b): basically the idea is to stop all processing, create some
types defined in the loaded assemblies, and call some methods on those types.
It may not be always possible if one of the types is running a tight loop on
a foreground thread. So the first thing to do is to try and kill all the
threads.
Regarding (c): The host we are writing should be able to swap an assembly
that is suspected to cause problems with an alternative one. Of course, this
is also possible by unloading the AppDomain and loading it with the same
assemblies except the suspected one. However, in our case under certain
scenarios this could be time consuming and as an alternative we are
investigating the possibility of swapping only the suspected assembly
without unloading the whole set.
Books recommendations: I've already read Jeff Richter’s <CLR via C#> (both
editions) and right now I am finishing <Customizing The Microsoft .Net
Framework CLR> by Steve Pratschner. <Shared Source .. > seems to be
interesting, thanks, I’ll have a look.
Again, thank you very much for your help. Where do you guys find time to
answer all our questions?:) You seem to be so willing to help; that’s quite
impressive..
Regards,
SS
Thank you for explaining it in details.
Oh, I was not aware of that the foreground UI thread will block the
AppDomain.Unload() method with CannotUnloadAppDomainException. Have you
invested to find out why the Thread.Abort did not terminate the target
thread? I have had a hard time to understand why the thread did not
terminate.
Anyway, if Application.Exit() can terminate all the foreground GUI threads
in the target AppDomain, I think we can call Application.Exit() to
terminate all the UI threads in the target AppDomain and followed with an
AppDomain.Unload() method calling to clean up the target AppDomain.
<Shared Source CLI Essentials> explains the CLI(which is very similiar with
CLR) in source-code level, so it has a deeper insight than other CLR books.
<Customizing The Microsoft .Net Framework CLR> targets the CLR hosting
topics in depth.
Actually, I am the .Net CLR and VS.net debugging newsgroups MSDN
subscription support engineer, so it's my job and pleasure to help you
:-).(Also, I learned a lot in the newsgroup discussion)
I agree with Jeffrey here, AppDomain.Unload() can/should not be blocked by a
running/blocked UI thread, in those cases where Thread.Abort did not
terminate the thread, the CLR would escalate to a rude abort which cannot be
blocked anyway.
Willy.
Thanks for replying. To answer your question whether I tried to find out why
Thread.Abort() didn’t terminate the foreground thread, - no, I didn’t even
try. First, I am lazy and I prefer to ask experts; this does save time in the
short run:) Second, the documentation on Thread.Abort says ‘The thread is not
guaranteed to abort immediately, or at all’ and that is enough for me (see
the first reason:))).
In my simplistic scenario the thread was doing something like this: while
(true) {}; so given the information I found at
http://www.ondotnet.com/pub/a/dotnet/2003/02/18/threadabort.html
I assumed there was no chance for that sort of thread to be aborted. Then I
asked myself a question why is it still aborted when it is created as a
background thread? I figured that trying to find an answer to this question
would take me an unjustified amount of time given the fact that stopping the
thread by Application.Exit() gives an immediate practical solution. I do need
to read the books you’ve recommended …
Regards,
SS
"Willy Denoyette [MVP]" wrote:
>
> I agree with Jeffrey here, AppDomain.Unload() can/should not be blocked by a
> running/blocked UI thread, in those cases where Thread.Abort did not
> terminate the thread, the CLR would escalate to a rude abort which cannot be
> blocked anyway.
>
> Willy.
Hi Willy,
Thanks for replying. I actually wasn’t talking about a UI thread, if I
understood your comment correctly. To avoid confusion, I’ve appended the code
that illustrates my point.
The code creates two forms, UI1 and UI2. Hit the ‘Start Thread’ button on
UI2 and then the ‘Unload AD2’ button on UI1. It doesn’t work every time on my
box, but in about half of all attempts I get the
CannotUnloadAppDomainException. To increase your chances:) you may have to
hit the ‘Start Thread’ several times to create more threads, especially if
you run this app outside VS.
Regards,
SS
.. and here is the code:
using System;
using System.Threading;
using System.Windows.Forms;
using System.Drawing;
using System.Reflection;
namespace AppDomains
{
public class Test : MarshalByRefObject
{
static void Main() {
Test t1 = new Test();
t1.StartUI1();
}
public void StartUI1() {
Thread t = new Thread(ShowUI1);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
private void ShowUI1() {
AppDomain ad2 = AppDomain.CreateDomain("AD2");
// You might need to change the type name here...
Test t2 =
(Test)ad2.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName,
"AppDomains.Test");
t2.StartUI2();
Form form = new Form {
Size = new Size(200, 200),
Text = "Form UI1"
};
Button b = new Button {
Text = "Unload AD2",
Location = new Point(5, 50)
};
b.Click += (s, e) => { AppDomain.Unload(ad2); };
form.Controls.Add(b);
Application.Run(form);
}
public void StartUI2() {
Thread t = new Thread(ShowUI2);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
private void ShowUI2() {
Form form = new Form {
Size = new Size(200, 200),
Text = "Form UI2"
};
Button b = new Button {
Text = "Start Thread",
Location = new Point(5, 50)
};
b.Click += (s, e) => {
Thread t = new Thread(T);
t.Start();
};
form.Controls.Add(b);
Application.Run(form);
}
private void T() {
while (true) { };
}
}
}
Thanks for your feedback.
I was able to reproduce this problem intermittently about 1 out of 5.
However, whenever I run it under windbg, the problem did not appear. I will
spend more time on this issue and get back to you ASAP. Thanks.
I'm not able to reproduce (running V2 of the FW), which is quite normal when
you run this code on a quad core as I do. However if you run this code on a
single core system, chances are higher to hit the problem, which is the
documented behavior (see AppDomain.Unload in the docs).
AppDomain.Unload is constrained by a time-out period, failing to unload
within that period of time will throw CannotUnloadAppDomainException.
Now, in the OP's case, there is a thread that continuously consumes all of
it's thread quanta, if you have only one CPU in the system, chances are high
that the "dedicated thread" responsible for unloading the AD, will fail to
inject a Thread.Abort in a timely fashion. On a multi-core, chances are much
lower to fail, while on a normally loaded quad core, chances are extremely
low to fail.
Willy.
Yes, I am running a single core PC and your explanation fits very well into
a simplistic mental picture of what was happening that I developed for
myself. Thank you very muich again.
Regards,
SS
Note that even on a multi-core, you aren't guaranteed to be able to unload
an AD, everything depends on the system's configuration and the CPU load,
and that's also what the doc tells us.
Even on a quad-core, I'm was able to reproduce the issue, this by starting
four threads like yours.
However, running with normal workload, you won't see this happen, and if you
do, you can always try to handle the exception and retry the AD unload.
Willy.
"Sunny S" <sun...@newsgroup.nospam> wrote in message
news:0BEEB1E9-4494-49A9...@microsoft.com...
This sounds like a promising explanation. Thank you for the sharing.
However, from principle, I am still a bit hard to understand it. The
started worker threads should run under the same priority as the GUI
threads. Even on the single-core CPU, OS kernel thread schedular will give
each thread the same time slice as long as these threads are in the same
priority. It is hard for me to understand why the CPU gives not enough time
to the GUI thread to call Thread.Abort() method.
Furthermore, if I remember correctly, the CPU will boost the UI thread's
priority making it more user friendly. So the GUI thread should have higher
priority(at least a small period) to execute the Thread.Abort() method.
Additionally, since the Thread.Abort() will cause the user-mode to
kernel-mode transition with stack-unwind which are both very consuming, I
suspect if this is another important factor for AppDomain.Unload() expires.
Anyway, I agree that real-world seldom causes this problem. Catching this
CannotUnloadAppDomainException and call AppDomain.Unload() again should
resolve this problem.
See inline.
Willy.
""Jeffrey Tan[MSFT]"" <je...@online.microsoft.com> wrote in message
news:$PxWyWGo...@TK2MSFTNGHUB02.phx.gbl...
> Hi Willy ,
>
> This sounds like a promising explanation. Thank you for the sharing.
>
Well, actually I'm not so sure it's a valid explanation, especially because
I can't seem to repro the issue, even not when running four threads on a
quad-core running Vista SP1.
> However, from principle, I am still a bit hard to understand it. The
> started worker threads should run under the same priority as the GUI
> threads. Even on the single-core CPU, OS kernel thread schedular will give
> each thread the same time slice as long as these threads are in the same
> priority. It is hard for me to understand why the CPU gives not enough
> time
> to the GUI thread to call Thread.Abort() method.
>
Apparently there is a dedicated thread that serves to unload AD's in V2 (see
the "Remarks" section in the AppDomain.Unload description), that means that
AD Unloads are not executed on the UI thread. I assume they would use the
Finalizer thread for this, but I can't confirm this for sure.
> Furthermore, if I remember correctly, the CPU will boost the UI thread's
> priority making it more user friendly. So the GUI thread should have
> higher
> priority(at least a small period) to execute the Thread.Abort() method.
>
True, but irrelevant when the Thread.Abort is issued by another (the
dedicated) thread that runs with "normal" priority level., if it's done by
the finalizer thread then there shouldn't be an issue as this one run at
real-time priority level.
Thank you for pointing out the document.
Yes, it seems that these issues are documented in MSDN clearly.
CannotUnloadAppDomainException is expected to throw in some scenarios.
Catching CannotUnloadAppDomainException and calling AppDomain.Unload again
should be the solution. Also, I think we'd better give a guard to this. For
example, we should check if this will result in an infinite
catch...re-calling loop. If so, I think we have to kill the threads in the
target AppDomain to recover from this situation. Yes, this may not be an
elegant solution, but we have to defense this situation.
Thanks.
Is there any supported way to enumerate all Threads? All Threads running
code from a particular AppDomain? All Threads started by a particular
AppDomain? Nevermind the problems with threads ignoring abort because they
are in an I/O call or other native code.
1. The Threads property of a Process instance should give you the set of
threads that are running in a given process or you can just use
Process.GetCurrentProcess.Threads.
2. Then for each thread in the ProcessThreadCollection returned by the
Threads property in (1) you can use the GetDomainID property to find which
AppDomains currently contain running threads and to map those threads to
individual AppDomains.
3. I am not sure if my speculations about your 3rd question are correct, but
I think that given the fact that AppDomains could be unloaded and then
recreated, even reused, I suspect, which is certainly true for threads
provided by the ThreadPool, associating an AppDomain that started a
particular thread with this thread or, the other way round, associating a
thread with an AppDomain that started this thread might lead to confusion
and/or could be costly. I doubt that the runtime supports this. The only
valid information would be provided by an instant snapshot of which threads
are running in which AppDomains (see 2 above).
Regards,
Sunny
I think #1, #2 provided Sunny is the .Net FCL's build-in support for the
thread/process enumeration. However, there is no much build-in support to
terminate/kill the freeze threads. Maybe we have to p/invoke
TermianteThread ourselves.(For example, .Net Process.Kill calls
TerminateProcess internally). We definite should use this as a last resort.
I was just trying to highlight the possibility that just because a thread is
currently running code in one AppDomain, doesn't mean it doesn't have other
AppDomains on the call stack and aborting the thread will interfere with
those other AppDomains. Or are cross-AppDomain calls always marshalled to a
thread dedicated to that AppDomain?
Consider for example the case where the plugin subscribed to an event in the
main program. When the main program fires the event handlers, the plugin
handler runs and hangs. How do you unload the AppDomain without throwing a
ThreadAbort exception up through the main program method which raised the
event?
What exactly do you mean with hangs? Does it mean that the thread has stack
in unmanaged land or what?
Willy.
There is no such thing as a thread being ‘dedicated to an AppDomain’. A
cross-AD call stays on a thread that originated the call; marshalling happens
between ADs, not between threads. Let’s say your thread TH1 is running in AD1
and you call a method on an object derived from MarshallByReference and
sitting in AD2. Then your call goes through the object’s proxy in AD1,
crosses domain boundary, and hits the real object in AD2 while still
remaining on TH1!
As far as the aborted thread’s call stack is concerned and possible
interference with ADs that might be on the stack, that’s exactly what
sometimes happens. After TH1 leaves AD1 and enters AD2, AD1 could be
unloaded. Unloading AD1 would result in ThreadAbortException being thrown on
TH1 even if it is in AD2! See for more details Steve Pratschner’s book
‘Customizing the Microsoft .NET Framework CLR’
Regarding your example when you have to kill a thread hanging in a plug-in
that sits in a different AD by unloading that AD, I am not sure, but somehow
unwinding ThreadAbortException doesn’t cross AD boundary even if other ADs
are on the thread’s call stack, so the original caller in a different AD
isn't hurt:)