Thread.Abort()如何工作?

Sri*_*vel 17 c# multithreading thread-abort

当无效输入传递给方法或对象即将进入无效状态时,我们通常抛出异常.让我们考虑以下示例

private void SomeMethod(string value)
{
    if(value == null)
        throw new ArgumentNullException("value");
    //Method logic goes here
}
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,我插入了抛出的throw语句ArgumentNullException.我的问题是运行时如何设法抛出ThreadAbortException.显然,throw在所有方法中都不可能使用语句,即使运行时也设法引入ThreadAbortException我们的自定义方法.

我想知道他们是怎么做到的?我很想知道幕后发生了什么,我打开了一个反射器打开Thread.Abort并结束了这个

[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern void AbortInternal();//Implemented in CLR
Run Code Online (Sandbox Code Playgroud)

然后我用Google搜索并发现这个ThreadAbortException如何真正起作用.这个链接说运行时通过QueueUserAPC函数发布APC ,这就是他们如何做到这一点.我不知道QueueUserAPC方法我只是试着看一下代码是否可行.以下代码显示了我的尝试.

[DllImport("kernel32.dll")]
static extern uint QueueUserAPC(ApcDelegate pfnAPC, IntPtr hThread, UIntPtr dwData);
delegate void ApcDelegate(UIntPtr dwParam);

Thread t = new Thread(Threadproc);
t.Start();
//wait for thread to start
uint result = QueueUserAPC(APC, new IntPtr(nativeId), (UIntPtr)0);//returns zero(fails)
int error = Marshal.GetLastWin32Error();// error also zero

private static void APC(UIntPtr data)
{
    Console.WriteLine("Callback invoked");
}
private static void Threadproc()
{
    //some infinite loop with a sleep
}
Run Code Online (Sandbox Code Playgroud)

如果我做错了什么原谅我,我不知道该怎么做.再回到问题,有关CLR团队的这个或部分知识的人可以解释它在内部是如何工作的吗?如果APC是技巧运行时跟随在这里做错了什么?

xan*_*tos 11

你确定你读过你指向的页面吗?最后归结为:

对Thread.Abort的调用归结为.NET在要中止的线程上设置一个标志,然后在线程生命周期的某些点期间检查该标志,如果设置了标志则抛出异常.


jos*_*ley 10

要使APC回调起作用,您需要一个线程句柄(与线程ID不同).我还更新了PInvokes的属性.

还要记住,线程需要处于"警报"等待状态才能调用APC(Thread.Sleep会给我们).因此,如果线程忙于执行操作,则可能无法调用它.

[DllImport("kernel32.dll", EntryPoint = "GetCurrentThread", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr GetCurrentThread();

[DllImport("kernel32.dll", EntryPoint = "QueueUserAPC", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern uint QueueUserAPC(ApcDelegate pfnAPC, IntPtr hThread, UIntPtr dwData);

[UnmanagedFunctionPointerAttribute(CallingConvention.StdCall)]
public delegate void ApcDelegate(UIntPtr dwParam);

[DllImport("kernel32.dll", EntryPoint = "DuplicateHandle", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern bool DuplicateHandle([In] System.IntPtr hSourceProcessHandle, [In] System.IntPtr hSourceHandle, [In] System.IntPtr hTargetProcessHandle, out System.IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAsAttribute(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions);

[DllImport("kernel32.dll", EntryPoint = "GetCurrentProcess", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern IntPtr GetCurrentProcess();


static IntPtr hThread;
public static void SomeMethod(object value)
{
    DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), out hThread, 0, false, 2);

    while (true)
    {
        Console.WriteLine(".");
        Thread.Sleep(1000);
    }
}

private static void APC(UIntPtr data)
{
    Console.WriteLine("Callback invoked");
}

static void Main(string[] args)
{
    Console.WriteLine("in Main\n");

    Thread t = new Thread(Program.SomeMethod);
    t.Start();

    Thread.Sleep(1000); // wait until the thread fills out the hThread member -- don't do this at home, this isn't a good way to synchronize threads...
    uint result = QueueUserAPC(APC, hThread, (UIntPtr)0);

    Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)


编辑:
CLR如何注入异常
给定线程函数的这个循环:

while (true)
{
    i = ((i + 7) * 3 ^ 0x73234) & 0xFFFF;
}
Run Code Online (Sandbox Code Playgroud)

然后我.Abort编写了线程并查看了本机堆栈跟踪

...
ntdll!KiUserExceptionDispatcher
KERNELBASE!RaiseException
clr!RaiseComPlusException
clr!RedirectForThrowControl2
clr!RedirectForThrowControl_RspAligned
clr!RedirectForThrowControl_FixRsp
csTest.Program.SomeMethod(System.Object)
...
Run Code Online (Sandbox Code Playgroud)

查看RedirectForThrowControl_FixRsp调用的返回地址,它指向循环的中间,没有跳转或调用:

nop
mov     eax,dword ptr [rbp+8]
add     eax,7 // code flow would return to execute this line
lea     eax,[rax+rax*2]
xor     eax,73234h
and     eax,0FFFFh
mov     dword ptr [rbp+8],eax
nop
mov     byte ptr [rbp+18h],1
jmp     000007fe`95ba02da // loop back to the top
Run Code Online (Sandbox Code Playgroud)

显然,CLR实际上正在修改有问题的线程的指令指针,以便从正常流程中物理地控制控制.他们显然需要提供几个包装器来修复和恢复所有堆栈寄存器以使其正常工作(因此适当命名_FixRsp_RspAlignedAPI.


在一个单独的测试中,我只是Console.Write()在我的线程循环中进行了调用,看起来CLR在物理调用之前注入了一个测试WriteFile:

KERNELBASE!RaiseException
clr!RaiseTheExceptionInternalOnly
clr! ?? ::FNODOBFM::`string'
clr!HelperMethodFrame::PushSlowHelper
clr!JIT_RareDisableHelper
mscorlib_ni!DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr)
mscorlib_ni!System.IO.__ConsoleStream.WriteFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Boolean)
Run Code Online (Sandbox Code Playgroud)