Как выяснить, кому принадлежит рабочий поток, который все еще работает, когда мое приложение закрывается? - PullRequest
11 голосов
/ 21 декабря 2010

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

alt text

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

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

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

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

Единственное, о чем я могу думать, это:

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

ОБНОВЛЕНИЕ

Возможно, эта информация будет полезна.Я решил использовать 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]

EDIT - не уверен, поможет ли это, но стек вызовов основного потока выглядит следующим образом:

    [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, и, возможно, это даст мне больше подсказок.

Ответы [ 5 ]

5 голосов
/ 28 декабря 2010

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

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

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

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

void Worker() {
    var win = new Window();
    // win.Closed += onWindowClose ?? ((o, e) => editor.Dispatcher.InvokeShutdown());
    editor.Show();
    System.Windows.Threading.Dispatcher.Run();
}
3 голосов
/ 28 декабря 2010

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

3 голосов
/ 21 декабря 2010

Идентификатор рабочего потока, который вы видите, равен 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);
    }
} 
1 голос
/ 21 декабря 2010

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

1 голос
/ 21 декабря 2010

Если вы создаете рабочие потоки (а не потоки пула), задайте для их Имя что-то описательное при создании.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...