17

通常、無効な入力がメソッドに渡されたとき、またはオブジェクトが無効な状態に入ろうとしているときに例外をスローします。次の例を考えてみましょう

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

上記の例では、 throws をスローする throw ステートメントを挿入しましたArgumentNullException。私の質問は、ランタイムがどのように をスローするかThreadAbortExceptionです。明らかにthrow、すべてのメソッドでステートメントを使用することはできません。ランタイムでさえThreadAbortException、カスタム メソッドをスローすることができます。

彼らはどうやってそれをするのだろうと思っていましたか?舞台裏で何が起こっているのか興味があったので、リフレクターを開いて開いたThread.Abortところ、これに行き着きました

[MethodImplAttribute(MethodImplOptions.InternalCall)]
private extern void AbortInternal();//Implemented in CLR

それから私はググって、このHow does ThreadAbortException really workを見つけました。このリンクは、ランタイムが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
}

何か間違ったことをしている場合は、私を許してください、私はそれを行う方法がわかりません。もう一度質問に戻りますが、これについての知識を持っている人、または CLR チームの一部が内部でどのように機能するかを説明できますか? APCトリックランタイムがここで間違っていることに従うのですか?

4

5 に答える 5

1

私はSSCLI コードをダウンロードし、色々調べてみました。コードを理解するのは困難ですが (ほとんどの場合、私は C++ や ASM の専門家ではないためです)、アボートが半同期的に挿入されるフックが多数見られます。

  • try/catch/finally/fault ブロックのフロー制御処理
  • GC アクティベーション (メモリの割り当て)
  • アラート可能状態の場合、ソフト割り込み (Thread.Interrupt など) を介してプロキシされます。
  • 仮想通話傍受
  • JITテールコールの準備
  • アンマネージドからマネージドへの移行

それはほんの数例です。私が知りたかったのは、非同期アボートがどのように注入されたかということでした。命令ポインターをハイジャックするという一般的な考え方は、それがどのように発生するかの一部です。ただし、これは上で説明したものよりもはるかに複雑です。Suspend-Modify-Resume のイディオムが常に使用されているようには見えません。SSCLI コードから、ハイジャックに備えて特定のシナリオでスレッドを一時停止および再開することがわかりますが、常にそうであるとは限りません。スレッドがフルボアで実行されているときにもハイジャックが発生する可能性があるようです。

リンク先の記事には、ターゲット スレッドに中止フラグが設定されていることが記載されています。これは技術的に正しいです。フラグが呼び出されTS_AbortRequested、このフラグの設定方法を制御する多くのロジックがあります。制約された実行領域が存在するかどうか、およびスレッドが現在 try-catch-finally-fault ブロックにあるかどうかを判断するためのチェックがあります。この作業の一部にはスタック クロールが含まれます。つまり、スレッドを中断して再開する必要があります。ただし、フラグの変化がどのように検出されるかが、本当の魔法が起こるところです。記事はそれをうまく説明していません。

上記のリストで、いくつかの準同期注入ポイントについて既に説明しました。それらを理解するのはかなり簡単です。しかし、非同期注入はどのように正確に行われるのでしょうか? 私には、JIT が幕の後ろにいる魔法使いのように見えます。JIT/GC には、収集が必要かどうかを定期的に判断する何らかのポーリング メカニズムが組み込まれています。これにより、マネージド スレッドのいずれかが状態を変更したかどうかを確認する機会も提供されます (中止フラグが設定されているなど)。TS_AbortRequestedが設定されている場合、ハイジャックはその場で発生します。

SSCLI コードを見ている場合は、ここにいくつかの優れた機能があります。

  • HandleThreadAbort
  • コモントリップスレッド
  • JIT_PollGC
  • JIT_TailCallHelper
  • COMPlusCheckForAbort
  • ThrowForFlowControl
  • JIT_RareDisableHelper

他にも多くの手がかりがあります。これは SSCLI であるため、メソッド名が本番環境で観察されたコール スタック ( Josh Poley が発見したものなど) と正確に一致しない場合がありますが、類似点はあることに注意してください。また、多くのスレッド ハイジャックはアセンブリ コードで行われるため、追跡が困難な場合があります。強調しJIT_PollGCたのは、ここで興味深いことが起こると信じているからです。これは、JIT が実行中のスレッドに動的かつ戦略的に配置すると思われるフックです。これは基本的に、これらのタイトなループがアボート インジェクションを受け取る方法のメカニズムです。ターゲット スレッドは実際にはアボート リクエストをポーリングしていますが、GC 1を呼び出すためのより大きな戦略の一部として

したがって、JIT、GC、およびスレッドのアボートが密接に関連していることは明らかです。SSCLI コードを見れば明らかです。たとえば、スレッド アボートのセーフ ポイントを決定するために使用される方法は、GC の実行が許可されているかどうかを決定するために使用される方法と同じです。


1 Shared Source CLI Essentials、David Stutz、2003 年、pg. 249-250

于 2013-08-09T20:49:56.467 に答える
1

QueueUserAPCを機能させるには、2 つのことを行う必要があります。

  1. 対象スレッドハンドルを取得します。これは、ネイティブ スレッド ID と同じではないことに注意してください。
  2. ターゲット スレッドがアラート可能な状態になることを許可します。

これを示す完全なプログラムを次に示します。

class Program
{
    [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();

    [DllImport("kernel32.dll")]
    private static extern IntPtr GetCurrentThread();

    [DllImport("kernel32.dll")]
    private static extern uint QueueUserAPC(ApcMethod pfnAPC, IntPtr hThread, UIntPtr dwData);

    private delegate void ApcMethod(UIntPtr dwParam);

    static void Main(string[] args)
    {
        Console.WriteLine("Main: " + Thread.CurrentThread.ManagedThreadId);
        IntPtr threadHandle = IntPtr.Zero;
        var threadHandleSet = new ManualResetEvent(false);
        var apcSet = new ManualResetEvent(false);
        var thread = new Thread(
            () =>
            {
                Console.WriteLine("thread started");
                threadHandle = GetCurrentThread();
                DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), out threadHandle, 0, false, 2);
                threadHandleSet.Set();
                apcSet.WaitOne();
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine("thread waiting");
                    Thread.Sleep(1000);
                    Console.WriteLine("thread running");
                }
                Console.WriteLine("thread finished");
            });
        thread.Start();
        threadHandleSet.WaitOne();
        uint result = QueueUserAPC(DoApcCallback, threadHandle, UIntPtr.Zero);
        apcSet.Set();
        Console.ReadLine();
    }

    private static void DoApcCallback(UIntPtr dwParam)
    {
        Console.WriteLine("DoApcCallback: " + Thread.CurrentThread.ManagedThreadId);
    }

}

これにより、開発者はメソッドの実行を任意のスレッドに挿入できます。ターゲット スレッドには、従来のアプローチで必要だったようなメッセージ ポンプが必要ありません。ただし、このアプローチの問題点の 1 つは、ターゲット スレッドがアラート可能な状態である必要があることです。したがって、基本的にスレッドは、APC キューを実行するためにThread.Sleep、 、 などの既定の .NET ブロッキング呼び出しの 1 つを呼び出す必要があります。WaitHandle.WaitOne

于 2013-08-29T18:41:13.457 に答える
0

It's easy, the underlying OS does it. If the thread is in any state except 'running on another core', there is no problem - it's state is set to 'never run again'. If the thread is runing on another core, the OS hardware-interrupts the other core via. it's interprocessor driver and so exterminates the thread.

Any mention of 'time-slice', 'quantum' etc. is just.....

于 2013-08-09T22:49:57.177 に答える