Как выяснить, кто владеет рабочим потоком, который все еще работает, когда мое приложение выходит?

Вскоре после обновления до VS2010 мое приложение не будет закрыто. Если я закрою приложение, а затем нажмите паузу в среде IDE, я вижу следующее:

alt text

Проблема в том, что нет никакого контекста. В стеке вызовов только говорится [Внешний код], что не слишком полезно.

Вот что я сделал до сих пор, чтобы попытаться сузить проблему:

  • удалены все посторонние плагины, чтобы свести к минимуму количество запущенных рабочих потоков.
  • установить точки останова в моем коде везде, где я создаю рабочие потоки (и делегаты + BeginInvoke, так как я думаю, что они все равно помечены как "рабочий поток" в отладчике). Ничего не было поражено.
  • set IsBackground = true для всех потоков

В то время как я мог бы сделать следующий шаг грубой силы, который должен вернуть мой код обратно в точку, где этого не произошло, а затем просмотреть все журналы изменений, это не очень эффективно. Может ли кто-нибудь рекомендовать лучший способ понять это, учитывая заметное отсутствие информации, представленной отладчиком?

Единственные другие вещи, о которых я могу думать, включают в себя:

  • прочитайте в WinDbg и попытайтесь использовать его для остановки в любое время, когда поток запущен. По крайней мере, я думал, что это возможно...:)
  • закомментируйте огромные блоки кода до тех пор, пока приложение не закроется должным образом, а затем начните раскомментировать, пока он этого не сделает.

UPDATE

Возможно, эта информация будет полезна. Я решил использовать WinDbg и подключиться к моему приложению. Затем я закрыл его и переключился на поток 0 и сбросил содержимое стека. Вот что у меня есть:

ThreadCount:      6
UnstartedThread:  0
BackgroundThread: 1
PendingThread:    0
DeadThread:       4
Hosted Runtime:   no
                                   PreEmptive   GC Alloc                Lock
       ID  OSID ThreadOBJ    State GC           Context       Domain   Count APT Exception
   0    1  1c70 005a65c8      6020 Enabled  02dac6e0:02dad7f8 005a03c0     0 STA
   2    2  1b20 005b1980      b220 Enabled  00000000:00000000 005a03c0     0 MTA (Finalizer)
XXXX    3       08504048     19820 Enabled  00000000:00000000 005a03c0     0 Ukn
XXXX    4       08504540     19820 Enabled  00000000:00000000 005a03c0     0 Ukn
XXXX    5       08516a90     19820 Enabled  00000000:00000000 005a03c0     0 Ukn
XXXX    6       08517260     19820 Enabled  00000000:00000000 005a03c0     0 Ukn
0:008> ~0s
eax=c0674960 ebx=00000000 ecx=00000000 edx=00000000 esi=0040f320 edi=005a65c8
eip=76c37e47 esp=0040f23c ebp=0040f258 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
USER32!NtUserGetMessage+0x15:
76c37e47 83c404          add     esp,4
0:000> !clrstack
OS Thread Id: 0x1c70 (0)
Child SP IP       Call Site
0040f274 76c37e47 [InlinedCallFrame: 0040f274] 
0040f270 6baa8976 DomainBoundILStubClass.IL_STUB_PInvoke(System.Windows.Interop.MSG ByRef, System.Runtime.InteropServices.HandleRef, Int32, Int32)*** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.30319_32\WindowsBase\d17606e813f01376bd0def23726ecc62\WindowsBase.ni.dll

0040f274 6ba924c5 [InlinedCallFrame: 0040f274] MS.Win32.UnsafeNativeMethods.IntGetMessageW(System.Windows.Interop.MSG ByRef, System.Runtime.InteropServices.HandleRef, Int32, Int32)
0040f2c4 6ba924c5 MS.Win32.UnsafeNativeMethods.GetMessageW(System.Windows.Interop.MSG ByRef, System.Runtime.InteropServices.HandleRef, Int32, Int32)
0040f2dc 6ba8e5f8 System.Windows.Threading.Dispatcher.GetMessage(System.Windows.Interop.MSG ByRef, IntPtr, Int32, Int32)
0040f318 6ba8d579 System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame)
0040f368 6ba8d2a1 System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame)
0040f374 6ba7fba0 System.Windows.Threading.Dispatcher.Run()
0040f380 62e6ccbb System.Windows.Application.RunDispatcher(System.Object)*** WARNING: Unable to verify checksum for C:\Windows\assembly\NativeImages_v4.0.30319_32\PresentationFramewo#\7f91eecda3ff7ce478146b6458580c98\PresentationFramework.ni.dll

0040f38c 62e6c8ff System.Windows.Application.RunInternal(System.Windows.Window)
0040f3b0 62e6c682 System.Windows.Application.Run(System.Windows.Window)
0040f3c0 62e6c30b System.Windows.Application.Run()
0040f3cc 001f00bc MyApplication.App.Main() [C:\code\trunk\MyApplication\obj\Debug\GeneratedInternalTypeHelper.g.cs @ 24]
0040f608 66c421db [GCFrame: 0040f608]

РЕДАКТИРОВАТЬ - не уверен, что это помогает, но главный стек вызовов потока выглядит следующим образом:

    [Managed to Native Transition]  
>   WindowsBase.dll!MS.Win32.UnsafeNativeMethods.GetMessageW(ref System.Windows.Interop.MSG msg, System.Runtime.InteropServices.HandleRef hWnd, int uMsgFilterMin, int uMsgFilterMax) + 0x15 bytes  
    WindowsBase.dll!System.Windows.Threading.Dispatcher.GetMessage(ref System.Windows.Interop.MSG msg, System.IntPtr hwnd, int minMessage, int maxMessage) + 0x48 bytes 
    WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame = {System.Windows.Threading.DispatcherFrame}) + 0x85 bytes 
    WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame) + 0x49 bytes  
    WindowsBase.dll!System.Windows.Threading.Dispatcher.Run() + 0x4c bytes  
    PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore) + 0x17 bytes  
    PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window) + 0x6f bytes 
    PresentationFramework.dll!System.Windows.Application.Run(System.Windows.Window window) + 0x26 bytes 
    PresentationFramework.dll!System.Windows.Application.Run() + 0x1b bytes 

Я сделал поиск по нему и нашел несколько сообщений, связанных с графическим интерфейсом WPF, и, возможно, это даст мне еще несколько подсказок.

Ответ 1

Идентификатор рабочего потока, который вы видите, равен 0. Это поток структуры и ожидается - это не поток, который программа "породила". Если вы подключитесь к любому. Net-процессу, вы увидите это. Я не уверен, какой фрейм-поток это - определенно не поток финализатора, так как это никогда не поток 0. Возможно, поток JIT?

Что более интересно, тогда ваш основной поток, поскольку это, кажется, висит. Я бы сосредоточился на отладке вашего основного потока до этого. Заблокирован ли он из оконного обработчика событий, ожидающего чего-то, чего никогда не произойдет?

Обновление

После прочтения трассировки стека для основного потока, добавленного в вопрос, было бы интересно запустить тест в нижней части, остановится ли основной поток или будет ли он просто бездействовать в ожидании сообщения (и что он ждет для WM_CLOSE, который он никогда не получал или никогда не отправлялся).

Приведенный ниже код можно использовать для ручной отправки сообщения WM_CLOSE в ваше приложение. Просто подождите, пока ваша программа зависает, когда вы ее отключите, а затем запустите код. Замените имя процесса своим.

Обновление 2

Хорошо, похоже, что основной поток хорошо и по-настоящему висел, поскольку он не обрабатывает сообщения WM_CLOSE или WM_QUIT.

Попробуйте сделать самое маленькое приложение, которое может воспроизвести проблему и опубликовать код.

Пример WM_CLOSE\WM_QUIT App

internal class Program
{
    private const int WM_QUIT = 0x0012;
    private const int WM_CLOSE = 0x0010;

    [DllImport("user32.dll")]
    private static extern bool PostMessage(int hhwnd, uint msg, IntPtr wParam, IntPtr lParam);

    private static void Main()
    {
        Process p = GetProcess("Your process name - no '.exe' required");

        CloseMainWindow(p);
    }

    private static Process GetProcess(string name)
    {
        List<Process> processes = Process.GetProcessesByName(name).ToList();

        if (processes.Count != 1)
        {
            throw new Exception(
              "Expected 1 process with name '" + name +
              "' but found " + processes.Count + ".");
        }

        return processes[0];
    }

    private static void CloseMainWindow(Process p)
    {
        PostMessage(p, WM_CLOSE, "close");
    }

    private static void QuitApplication(Process p)
    {
        PostMessage(p, WM_QUIT, "quit");
    }

    private static void PostMessage(Process p, uint message, string name)
    {
        Console.WriteLine("Posting {0} message to '{1}'...", name, p.ProcessName);

        bool succeeded = PostMessage(p.MainWindowHandle.ToInt32(), message, IntPtr.Zero, IntPtr.Zero);

        Console.WriteLine("Posted {0} message to '{1}' (succeeded:{2}).", name, p.ProcessName, succeeded);
    }
} 

Ответ 2

Добавьте следующий обработчик в каждое окно, созданное вашим приложением в отдельном потоке:

win.Closed += (o, e) => win.Dispatcher.InvokeShutdown();

Если главный поток зависает, вызовите win.Dispatcher.InvokeShutdown() в MainWindow.Closed - это автоматически закроет все остальные окна, созданные в основном потоке.

Без этого обработчика мое приложение со следующим кодом также висит на выходе:

void Worker() {
    var win = new Window();
    // win.Closed += onWindowClose ?? ((o, e) => editor.Dispatcher.InvokeShutdown());
    editor.Show();
    System.Windows.Threading.Dispatcher.Run();
}

Ответ 3

Я, наконец, понял эту проблему. У меня был контроль, который был Import изменен MEF, но на самом деле никогда не назывался (пока). Я думаю, что MEF создавал его, даже если он нигде не упоминался (я был в предположении, что создание не произошло до тех пор, пока не был запрошен ресурс, но, видимо, я ошибся). Я исправил проблему, используя Lazy < > instantication, и теперь она работает. Это действительно бросило меня, но спасибо всем за помощь. Я многому научился отлаживать эту проблему.

Ответ 4

Если вы создаете рабочие потоки (а не пул потоков), установите Name в описание, описывающее создание.

Ответ 5

Добавить регистрацию ко всему вашему коду для регистрации метода и идентификатора потока. Вы можете использовать замену регулярных выражений, чтобы поставить ее в начале каждого отдельного метода. Затем запустите приложение и выключите его. Посмотрите, какие сообщения журнала имеют тот же идентификатор потока, что и рабочий поток, который не отключается.