关闭后,我的 Windows 窗体应用程序仍在作为任务运行

Posted

技术标签:

【中文标题】关闭后,我的 Windows 窗体应用程序仍在作为任务运行【英文标题】:My Windows Forms Application is still running as task after closing 【发布时间】:2019-08-16 19:18:08 【问题描述】:

我已经使用 Visual Studio 2010 构建了一个 C# Windows 窗体应用程序。

运行和关闭应用程序在短时间内成功完成。 该进程未在任务管理器中运行。以及调试过程关闭。没有问题发生。

但是如果应用程序正在运行一段时间,程序不会关闭并且仍然在任务管理器中运行 (我只是打开应用程序,什么都不做,只是等待几个小时来重现问题)。 在调试模式下,我必须点击 STOP DEBUGGING 按钮才能结束进程。

我该怎么做才能找到根本原因?

【问题讨论】:

你的应用程序是做什么的? 它只是在等待用户输入,然后从 DataContext 实例中获取数据。 你试过在调试器上按下暂停键吗?这可能会在仍然保持程序打开的语句上暂停。 是的,我收到并收到消息:没有可用的源和没有可用的反汇编。 您应该发布您的代码。你说它“只是在等待用户输入”——如果这是真的,你为什么希望代码完成?如果您无法发布您的代码,您将不得不通过单步调试它,并查看它可能被阻止的位置。 【参考方案1】:

如果您的项目包含超过 1 个表单,您应该转到最后一个表单的事件并双击“FormClosed”事件。在此操作将您发送到代码后,请在括号中写入:

 Application.Exit(); 

【讨论】:

【参考方案2】:

一个进程在所有前台线程停止后结束。

在典型的 Winforms 应用程序中,有一个主前台线程 - UI 线程。这会在主窗体(Application.Run 中使用的那个)关闭后停止。之后检查你的Main 方法在做什么,或者只是在那里放一个断点来查看线程是否成功

如果您正在执行多线程,您可能还会有一些前台工作线程。你有责任确保它们都被阻止。棘手的部分是您正在使用的某些类可能会在您不知情的情况下自行启动此类线程。首先要记住的是,您创建的任何实现IDisposable 的对象实际上都应该被释放。这可能会解决这个问题。一个经常引起麻烦的例子是System.Threading.Timer(或System.Timers.Timer)——如果你不Dispose它,它会让你的应用程序无限期地运行。

要调查此问题,您可以使用 Visual Studio 调试器中的线程列表(调试 -> Windows -> 线程)。运行应用程序,根据需要等待,关闭表单,然后暂停调试器。线程列表将显示进程中的所有托管线程。查看正在运行的线程上的位置 - 双击一个线程会将您的调试器视图切换到该线程,然后您可以看到调用堆栈。这可能会让您了解该线程的来源,以及它当前正在执行的代码(即为什么它被卡住了)。您可能会在某处看到等待(除非它实际上在做 CPU 工作);只需查看调用堆栈(调试 -> Windows -> 调用堆栈)并查找要识别的内容。

如果您检查了所有线程,并且在调用堆栈中看不到任何可疑内容,则可能需要在调试器中进行一些配置。您可以尝试两件主要的事情 - 首先,在调用堆栈窗口中,右键单击并选择“显示外部代码”。如果这没有帮助,您可能必须禁用 Just My Code (Options -> Debugger),并为所涉及的模块启用符号加载。这有点复杂。

【讨论】:

【参考方案3】:

你可以使用 Environment.Exit(0); 反而。

【讨论】:

【参考方案4】:

如果该过程仍处于待处理状态,则意味着您没有正确处理资源。

使用Application.Exit()或要求系统执行Environment.Exit(0)可能会在发生错误时登录系统,如果你想知道如何正确关闭进程而不是依赖Application.Exit()关闭您的应用程序的线程,您必须知道如何收集这些垃圾。

您可以重新实现Dispose 方法来处理服务、套接字、流以及几乎所有具有.Dispose 可用的东西。

 public class MyClass: IMyClass, IDisposable
    
        private bool _disposed = false;
        // ...
public void Dispose()

    Dispose(true);

    GC.SuppressFinalize(this);

protected virtual void Dispose(bool disposing)

    if (_disposed) return;

    if (disposing)
    
        // dispose your stuff you created in this class
        // do the same for other classes

        // some examples
        /*
        _webClient.Dispose();
        _connector.DataAvailable -= ConnectorHasDataComing
        _socket.Dispose();
        _timer.Dispose();
        _taskLogs.ForEach(x => 
            x.Token.Cancel();
            x.Task.Wait();
            x.Task.Dispose();
        );
        */
    

    // dispose native events

    _disposed = true;

如果您使用 System.Threading.ThreadSystem.Threading.Tasks.TaskSystem.IO.MemoryStream(或其他类型的流 - 写入器/读取器),以及需要 CancellationTokenSource 的其他类型。如果您在处置类时在类中创建了资源,请在调用.Dispose()之前使用Token.Cancel() 方法让它知道它的父级正在被处置,并为它.Wait() 告知它。

public async Task Run(CancellationTokenSource cancellationTokenSource)

    // ...
    while (Running) 
        if (cancellationTokenSource.IsCancellationRequested) return;
        // ....
    

    // ....
    using (var reader = new WaveFileReader(tempFile))
    
         reader.Position = 0;
         await reader.CopyToAsync(fileWriter,81920, cancellationTokenSource.Token);
    

当我的调试在关闭应用程序后仍处于待处理状态时,我使用诊断工具发现了我的问题。

如果您使用 CPU 使用率,您可以单击 Break All 并设置断点。 然后您可以查看分析器并找到最重要的功能,您可能会发现您的表单已被释放,但您有一个调用表单上的字段的线程或任务。

就我而言,我使用的是文件写入器,并且在该类中实现了 IDisposable,但它有时是关于或实际使用 .copyTo 在文件读取器和自身之间进行数据传输,因此它处于挂起状态而不会引发异常。

点击一个事件后,点击Go to Source code并放置一个断点,你可能会看到你的代码存放的事件。

否则,您可以在同一工具中使用选项卡Memory Usage 拍摄快照并查看堆和对象差异或选项卡CPU Usage 并查看记录的配置文件。如果以这种方式找到我的copyTo 问题。

您也可以使用Throw on all exceptions 运行您的应用程序

【讨论】:

以上是关于关闭后,我的 Windows 窗体应用程序仍在作为任务运行的主要内容,如果未能解决你的问题,请参考以下文章

关闭后 JavaFX 应用程序仍在运行

winform程序登陆后关闭登录窗体

应用程序关闭后后台进程(AsyncTask)仍在运行

WinForm程序关闭窗体后杀死进程

是否有任何在 Windows 窗体应用程序中使用 CefGlue 或 CefSharp 的示例且设置最少?

调用 Application.Exit() 后,应用程序仍在内存中运行