如何找出谁拥有我的应用程序退出时仍在运行的工作线程?

Dav*_*ave 11 c# wpf multithreading .net-4.0 visual-studio-2010

升级到VS2010后不久,我的应用程序就不会干净地关闭.如果我关闭应用程序然后在IDE中点击暂停,我会看到:

替代文字

问题是,没有背景.调用堆栈只是说[外部代码],这没有太大帮助.

这是我到目前为止试图缩小问题的方法:

  • 删除所有无关的插件以最小化启动的工作线程数
  • 在我创建工作线程的任何地方设置断点(和委托+ BeginInvoke,因为我认为它们在调试器中被标记为"工作线程").没有人被击中.
  • 为所有线程设置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]
Run Code Online (Sandbox Code Playgroud)

编辑 - 不确定这是否有帮助,但主线程的调用堆栈如下所示:

    [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 
Run Code Online (Sandbox Code Playgroud)

我对它进行了搜索,发现了一些与WPF GUI挂起的帖子,也许这会给我一些更多的线索.

Myk*_*iuk 5

将以下处理程序添加到应用程序在单独线程中创建的每个窗口:

win.Closed += (o, e) => win.Dispatcher.InvokeShutdown();
Run Code Online (Sandbox Code Playgroud)

如果主线程挂起,称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();
}
Run Code Online (Sandbox Code Playgroud)


Tim*_*oyd 3

您看到的工作线程 ID 是 0。这是一个框架线程并且是预期的 - 它不是程序“生成”的线程。如果您附加到任何 .Net 进程,您都会看到这一点。我不确定它是哪个框架线程 - 绝对不是终结器线程,因为它永远不是线程 0。也许是 JIT 线程?

更有趣的是你的主线程,因为它似乎是挂着的。我会集中精力调试你的主线程以解决这个问题。例如,它是否因窗口关闭事件处理程序而陷入僵局,等待永远不会发生的事情?

更新

在阅读了添加到问题中的主线程的堆栈跟踪后,运行一个测试来了解主线程是否已停止或者是否只是空闲等待消息(并且正在等待 WM_CLOSE它从未收到或从未发送)。

下面的代码可用于手动向您的应用程序发送 WM_CLOSE 消息。只需等待程序在关闭后挂起,然后运行代码即可。将进程名称替换为您自己的进程名称。

更新2

好的,看起来主线程确实挂起了,因为它没有处理 WM_CLOSE 或 WM_QUIT 消息。

请尝试制作可以重现问题的最小应用程序并发布代码。

WM_CLOSE\WM_QUIT 应用程序示例

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);
    }
} 
Run Code Online (Sandbox Code Playgroud)