Soru Thread.Abort () nasıl çalışır?


Geçersiz giriş bir yönteme aktarıldığında veya bir nesne geçersiz duruma geçmek üzereyken genellikle istisna atarız. Aşağıdaki örneği ele alalım

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

Yukarıdaki örnekte, atılan bir throw ifadesi ekledim ArgumentNullException. Sorum şu: çalışma zamanı nasıl atıyor? ThreadAbortException. Açıkçası, bir throw tüm yöntemlerde deyim, çalışma zamanı bile atmayı başarıyor ThreadAbortException özel yöntemlerimizde de.

Nasıl yaptıklarını merak ediyordum? Sahnelerin ardında neler olduğunu merak ettim, açmak için bir reflektör açtım Thread.Abort ve bununla biter

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

Sonra ben googled ve bunu buldum ThreadAbortException gerçekten nasıl çalışır?. Bu bağlantı, çalışma zamanının APC aracılığıyla yayınlandığını söylüyor QueueUserAPC fonksiyon ve hile böyle yapar. Farkında değildim QueueUserAPC yöntem Ben sadece bazı kod ile mümkün olup olmadığını görmek için bir deneme verdim. Aşağıdaki kod benim denememi gösterir.

[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
}

Yanlış bir şey yapmak beni affederse, nasıl yapılacağını bilmiyorum. Tekrar sorulacak, bu veya CLR ekibinin bir kısmı hakkında bilgi sahibi olan biri, bunun dahili olarak nasıl çalıştığını açıklayabilir mi? Eğer APC hile runtime burada ne yapıyorum takip ediyor?


17
2017-08-09 19:22


Menşei


Dahili olarak nasıl çalıştığını bilmiyorum, ama neden bilmek istediğini merak ediyorum. Sadece genel bilgi için ise, bunu anlayabilirim. Ama umarım plan yapmazsın kullanım Bu bir şey için hile. Bir iş parçacığı iptal etmek yapmak çirkin ve potansiyel olarak tehlikeli bir şeydir. - Jim Mischel
@JimMischel Hayır asla, Abort'un bir kötülük olduğunu biliyorum, Sadece nasıl çalıştığını bilmek merak ediyorum. - Sriram Sakthivel
@SriramSakthivel mono kaynaklara bakmayı denediniz mi? (mono-project.com) - Andrew Savinykh
@zespri Başka bir impl için geçtim, buna bir deneyeyim. - Sriram Sakthivel
Ara işlemci sürücüsü iletişimi. Durdurulması gereken iş parçacığını çalıştıran CPU çekirdeği, bir çekirdek içi sürücü aracılığıyla donanıma engel olur. Donanımın nasıl çalıştığını anlamalısınız. - Martin James


Cevaplar:


İşaretlediğiniz sayfayı okuduğunuzdan emin misiniz? Sonunda aşağı kaynar:

Thread.Abort çağrısı, bir iş parçacığı üzerinde iptal edilmek üzere bir bayrağın ayarlanması için .NET'e döner ve ardından söz konusu bayrağın, iş parçacığının yaşam süresi içinde belirli noktalarda kontrol edilmesini sağlar ve bayrak ayarlanmışsa istisnayı atar.


10
2017-08-09 19:27



Evet, ancak iş parçacığı yöntemimi yürütürken, çalışma zamanının içinde bir istisna atmayı nasıl başarabildiğini? - Sriram Sakthivel
@SriramSakthivel Ne yazık ki o sitede yazılmış açıklama biraz kafa karıştırıcı, çünkü bir adımda çok fazla atlıyor ... iş parçacığı uyuyor, ama bir sonraki adımda Thread.Abort iş parçacığının başka bir durumda gitmesini bekler. - xanatos
@SriramSakthivel "Eşzamansız İstisna" anahtar kelimesine gitmek sizi doğru yolda bulabilir. - dialer
Sonunda, bunun çok karmaşık olduğunu sanmıyorum ... Bir iş parçacığı çalışmıyorsa, kayıtları bir yere kaydedilir. Oraya gidip Program Sayacını attığınız yere yönlendirin. İş parçacığı PC'nize bir dahaki sefere yüklenir ve CPU atmaya "atlar". (Program Sayacı aka Talimat Sayacı, geçerli yürütme talimatının adresiyle kayıttır) - xanatos
Yani Jit atmak için makine kodunu anında değiştirecek ThreadAbortException? - Sriram Sakthivel


APC geri aramanızın işe yaraması için, bir iş parçacığı tanıtıcısına (iş parçacığı kimliğiyle aynı değil) ihtiyacınız vardır. Ayrıca PInvokes'deki özellikleri güncelledim.

Ayrıca, APC'nin çağrılabilmesi için iş parçacığının "uyarılabilir" bekleme durumunda olması gerektiğini unutmayın (hangi Thread.Sleep bize verir). Yani iş parçacığı meşgul şeyler yapıyorsa, bu çağrılmayabilir.

[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();
}


Düzenle:
CLR, özel durumu nasıl enjekte eder?
İş parçacığı işlevi için bu döngü verildiğinde:

while (true)
{
    i = ((i + 7) * 3 ^ 0x73234) & 0xFFFF;
}

Ben o zaman .Abortiş parçacığını düzenleyin ve yerel yığın izine bakın

...
ntdll!KiUserExceptionDispatcher
KERNELBASE!RaiseException
clr!RaiseComPlusException
clr!RedirectForThrowControl2
clr!RedirectForThrowControl_RspAligned
clr!RedirectForThrowControl_FixRsp
csTest.Program.SomeMethod(System.Object)
...

İade adresini arıyorum RedirectForThrowControl_FixRsp çağrı, benim atlamaların veya çağrıların bulunmadığı döngünün ortasına işaret ediyor:

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

Görünüşe göre, CLR aslında söz konusu iş parçacığının talimat işaretçisini normal akıştan fiziksel olarak kontrol etmek için değiştiriyor. Açıkçası, düzgün bir şekilde çalışmasını sağlamak için tüm yığın kayıtlarının düzeltilmesi ve geri yüklenmesi için birkaç sarıcı tedarik etmeleri gerekiyordu. _FixRsp ve _RspAligned API'leri.


Ayrı bir testte, daha yeni Console.Write() benim iş parçacığı döngü içinde aramalar ve orada CLR fiziksel arama için hemen önce bir test enjekte gibi görünüyordu 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)

10
2017-08-29 18:13



Sağol, Benim kötü. Kullanıyordum ThreadId sap yerine. Şimdi apc çalışıyor. Ama bu benim soruma tamamen cevap vermiyor. İş parçacığı uyarılabilir durum girmediğinde bile çalışma zamanı istisnayı nasıl yönetir (Uyku olmadan bazı sonsuz döngü)? - Sriram Sakthivel
Ha ... bana yumruk atıyormuş gibi görünüyorsun! İyi iş! - Brian Gideon
@BrianGideon ve @josh Şaşırtıcı bir şekilde, bu sorunun birkaç gününden sonra, aynı anda her ikinize de benzer cevaplar alıyorum. İkinize de çok teşekkür ederim çocuklar. Burada aptalca kaçırılan bir iş parçacığı tanıtıcısıyım :(. Herhangi biri uyarılabilir durum çalıştırma zamanı girmeden açıklayabilir mi? ThreadAbortException, Nasıl yaparlar? - Sriram Sakthivel
@SriramSakthivel: Düşünmemi sağlayan gerçekten iyi bir soru sordun. Ben sizin gibi, abortun nasıl enjekte edildiğinin ayrıntılarını hala bilmek isterim. Sanırım "iptal edilmek" bayrağı kadar basit değil. Ayrıca, benim bir göz atın soru Burada, işten çıkarmaların tam olarak nasıl faydalı olacağını bilerek başka bir bakış açısı için. - Brian Gideon
Evet bu bana çok düşünmemi sağladı ama ipucu yok. Katı bir cevap beklememek bekleniyor. Buna cevap verecek konumda kim olacak? @EricLippert ile nasıl iletişime geçilir? btw son 1 saat boyunca sorunuza bakıyorum. - Sriram Sakthivel


Almak için QueueUserAPC Çalışmak için iki şey yapmalısın.

  1. Hedef iplik tutamağını edinin. Bunun yerel iş parçacığı kimliği ile aynı şey olmadığını unutmayın.
  2. Hedef iş parçacığının uyarılabilir duruma geçmesine izin verin.

İşte bunu gösteren tam bir program.

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);
    }

}

Bu, bir geliştiricinin herhangi bir isteğe bağlı iş parçacığına bir yöntemin yürütülmesini enjekte etmesine izin verir. Hedef parçacığın geleneksel yaklaşım için gerekli olabilecek bir mesaj pompasına sahip olması gerekmez. Ancak bu yaklaşımla ilgili bir problem, hedef parçanın uyarılabilir bir durumda olması gerektiğidir. Yani temelde iş parçacığı gibi konserve. NET engelleme çağrıları birini çağırmak gerekir Thread.Sleep, WaitHandle.WaitOneAPC kuyruğunun yürütülmesi için vb.


1
2017-08-29 18:41



APC'yi temizlediğiniz için teşekkürler Brian, ama asıl sorun hala aynı yerde bulunuyor. Uyarılabilir durumu olmadan çok çalışma zamanı atar ThreadAbortException Bu benim bilgimin ötesinde bir şey :( - Sriram Sakthivel
İlginç bir şekilde, GetCurrentThread () ile aynı hatayı aldım. Bir sözde tanıtıcı döndürdüğünden, aslında ana iş parçacığından kullandığınızda, aslında APC'yi ana iş parçacığının sırasına göre sıralar. Kodumu tutamacı çoğaltmak için değiştirdim, bu yüzden sözde tanıtıcı kullanmazdım. - josh poley
@joshpoley: Oh haklısın! Bunu fark etmedim bile. Benim örneğim şimdi düzeltildi. Şimdi cevabı güncellemeyi sindirmem gerekiyor. Bazı iyi ipuçları buluyor gibi görünüyor. - Brian Gideon
Brian ve @Josh Siz ikiniz de birbirinizle bir araya oturuyorsunuz ve cevaplar, aynı tür düzenlemeler gönderiyorsunuz. Ne tesadüf! - Sriram Sakthivel
Sanırım anladım. Bir iş parçacığının yeni bir zaman dilimi aldığı, ancak yürütme işlemine devam etmeden önce dikkat edilmesi gereken nokta da uyarılabilir bir noktadır. Ancak MSDN belgeleri açıkça bunu belirtmiyor. - confusopoly


Kolay, temel işletim sistemi bunu yapıyor. Eğer iş parçacığı 'başka bir çekirdek üzerinde koşma' dışında herhangi bir durumda ise, sorun yoktur - bu durum 'asla tekrar koşma' durumuna getirilir. İş parçacığı başka bir çekirdekte çalışıyorsa, işletim sistemi donanımı diğer çekirdeği arayarak keser. o süreçler arası sürücü ve böylece iş parçacığı yok eder.

'Zaman dilimi', 'kuantum' vb. Herhangi bir ifade sadece .....


0
2017-08-09 22:49



İşletim sisteminin kendisinin yönetilen bir iş parçacığı çalıştırdığınız konusunda hiçbir fikri yoktur ve yönetilen bir özel durum atmak için denetlediği bazı özel durumları yoktur. - josh poley


İndirdim SSCLI kodu ve etrafta dolanmaya başladı. Kodun benim için takip etmesi zor (çoğunlukla C ++ veya ASM uzmanı olmadığım için) ama çok yarışmaların yarı eşzamanlı olarak enjekte edildiği kancalar.

  • try / catch / finally / fault blok akış kontrol işlemi
  • GC aktivasyonları (bellek ayırma)
  • uyarılabilir bir durumda iken yumuşak kesmeler (Thread.Interrupt ile) ile proxy edildi
  • sanal arama engelleri
  • JIT kuyruk arama hazırlıkları
  • yönetilen geçişlere yönetilmeyen

Bu sadece birkaç isim. Bilmek istediğim, asenkron alıştırmaların nasıl enjekte edildiğiydi. Talimat göstericisini ele geçirme genel fikri, bunun nasıl gerçekleştiğinin bir parçası. Bununla birlikte, yukarıda tarif ettiğimden çok daha karmaşıktır. Suspend-Modify-Resume deyiminin her zaman kullanıldığı görünmüyor. SSCLI kodundan, kaçırma için hazırlanmak üzere belirli senaryolarda ipliği askıya aldığını ve devam ettirdiğini görebiliyorum, ancak bu her zaman böyle değildir. İş parçacığı tam delik de çalışırken kaçırmanın ortaya çıkabileceğini bana gösteriyor.

Bağlantınız olan makale, iptal ipinin hedef konuya ayarlandığından bahseder. Bu teknik olarak doğru. Bayrak denir TS_AbortRequested ve bu bayrağın nasıl ayarlandığını kontrol eden bir çok mantık var. Kısıtlı bir yürütme bölgesinin var olup olmadığını ve ipliğin şu anda bir deneme-son-hata bloğu içinde olup olmadığını belirlemek için kontroller vardır. Bu çalışmanın bir kısmı, iş parçacığının askıya alınması ve yeniden başlatılması gerektiği anlamına gelen bir yığın taramayı içerir. Ancak, bayrağın değişimi tespit edildiğinde gerçek sihir gerçekleşir. Makale çok iyi açıklamıyor.

Yukarıdaki listede birkaç yarı senkron enjeksiyon noktasından bahsetmiştim. Anlamak için oldukça önemsiz olmalı. Ancak, asenkron enjeksiyon tam olarak nasıl gerçekleşir? Eh, bana öyle geliyor ki JIT buradaki perdenin arkasındaki büyücü. Bir koleksiyonun oluşup oluşmayacağını periyodik olarak belirleyen JIT / GC'ye yerleştirilen bir çeşit yoklama mekanizması vardır. Bu ayrıca yönetilen iş parçacıklarının herhangi birinin durumunu değiştirip değiştirmediğini (iptal bayrağı ayarlanmış gibi) kontrol etme olanağı sağlar. Eğer TS_AbortRequestedayarlanır sonra o zaman ve orada bir kaçırma olur.

SSCLI koduna bakıyorsanız, bakmanız gereken bazı iyi işlevler vardır.

  • HandleThreadAbort
  • CommonTripThread
  • JIT_PollGC
  • JIT_TailCallHelper
  • COMPlusCheckForAbort
  • ThrowForFlowControl
  • JIT_RareDisableHelper

Başka birçok ipucu var. Bunun SSCLI olduğunu aklınızdan çıkarmayın, böylece yöntem isimleri üretimde gözlemlenen çağrı yığınlarıyla tam olarak eşleşmeyebilir. Josh Poley keşfetti), ama benzerlikler olacak. Ayrıca, bir çok iş parçacığı ele geçirme montaj kodu ile yapılır, bu yüzden zaman zaman takip etmek zordur. vurguladım JIT_PollGC çünkü bu ilginç şeylerin olduğu yer olduğuna inanıyorum. Bu, JIT'in dinamik ve stratejik olarak yürütme iş parçasına yerleştirileceğine inandığım kanca. Bu temel olarak, bu sıkı döngülerin iptal enjeksiyonlarını nasıl alabileceğinin mekanizmasıdır. Hedef iş parçacığı gerçekten de, iptal isteğinde bulunmayı, ancak GC'yi çağırmak için daha büyük bir stratejinin bir parçası olarak yoklamayı amaçlıyor.1

Öyleyse, JIT, GC ve iş parçacığı iptalleri yakından ilişkilidir. SSCLI koduna baktığınızda belli oluyor. Örnek olarak, iş parçacığı iptalleri için güvenli noktaları belirlemek için kullanılan yöntem aynı GC'nin çalışmasına izin verilip verilmediğini belirlemek için kullanılır.


1Paylaşılan Kaynak CLI Essentials, David Stutz, 2003, pg. 249-250


0
2017-08-09 20:49



Hayır, bu şekilde çalışmıyor :) - Hans Passant