设为首页 加入收藏

TOP

是什么在.NET程序关闭时阻碍进程的退出?(一)
2019-09-03 03:16:09 】 浏览:37
Tags:什么 .NET 程序 关闭 阻碍 进程 退出

在平时使用软件或是.NET程序开发的过程中,我们有时会遇到程序关闭后但进程却没有退出的情况,这往往预示着代码中有问题存在,不能正确的在程序退出时停止代码执行和销毁资源。这个现象有时并不容易被察觉,但在另一些情况下却会产生影响软件功能的Bug。本文列举可能影响.NET程序进程退出的因素,并用几个小例子说明这些因素如何导致Form Application和Windows Service的Bug。

一、进程不能退出对于某些Windows Form程序的影响

在传统C/S结构的系统中,客户端会通过Socket或WCF服务利用特定的端口与服务端保持通信。因此在很多应用场景中,为避免端口冲突,单台计算机同一时刻只允许启动一个客户端,这也符合一个客户端代表单个用户角色的业务设计。这可以通过Mutex类,或者在客户端启动时检查是否已有同名的进程存在来实现。有些客户端启动逻辑被设计成当存在已有进程时,不初始化用户界面,而是自动切换到已经打开的客户端并关闭自身。

在这种情况下,如果前一次从客户端界面中退出,但是进程没有关闭,那随后再次启动客户端时就再也无法正常显示出用户界面,除非手动杀掉进程再次启动。

二、Foreground线程导致进程无法退出的例子

用如下代码来模拟进程无法退出的情况。简单起见,这个小窗口程序没有任何网络或数据库操作,仅仅是用一个线程定时刷新UI。设想是当程序界面构建完成后启动一个Thread,随后每隔1秒刷新当前时间,当点击窗体关闭按钮之后,程序退出,Thread和进程一同被销毁。

 1 public partial class Form1 : Form
 2 {
 3     Thread worker = null;
 4 
 5     public Form1()
 6     {
 7         InitializeComponent();
 8         Load += new EventHandler(Form1_Load);
 9     }
10 
11     void Form1_Load(object sender, EventArgs e)
12     {
13         worker = new Thread(new ThreadStart(DoWork));
14         worker.Start();
15     }
16 
17     private void DoWork()
18     {
19         while (true)
20         {
21             Thread.Sleep(1000);
22             if (IsHandleCreated && !IsDisposed)
23             {
24                 Invoke((MethodInvoker)(() => label1.Text = DateTime.Now.ToString()));
25             }
26         }
27     }
28 }

在关闭窗体之后,实际的运行结果却是,用户看不到任何界面,但进程一直停留在任务管理器中,Thread也没有停止工作。

本例中,进程无法退出的原因就在于worker线程的IsBackground属性。创建Thread时没有对它赋值,IsBackground就保留它的默认值false,这种方式启动的线程也叫前台线程。可以看出,从Thread类创建出来的线程默认为前台线程。按照MSDN的解释,前台线程与后台线程唯一的区别,就是前者在完成执行代码之前会阻止进程的终止。也即.NET进程在退出时,会先等待前台线程执行完所有的操作,而后直接终止正在运行中的后台线程。

三、什么情况下使用Foreground线程

由于Background线程在进程程退出时被立即中止可能导致处理中断或数据丢失,当线程处理的任务和数据比较重要时,需要考虑用Foreground线程。例如希望退出程序时仍然能完整保存数据,或者在退出时需要完成到服务器的数据上传工作,或者需要确保某些资源得以释放。而在另一些情况下,如果线程执行的任务在并不是非常重要,则可以考虑用Background线程,如监听网络通信或临时计算任务等。

.NET中有多种方式可以创建或使用一个新线程,除了Thread类之外,还有ThreadPool.QueueUserWorkItem方法、BackgroundWorker类、Task类、Parallel类以及各种Timer。在这之中,只有从Thread类创建出来的线程才会默认是Foreground,其它的类多数是使用线程池中的线程来执行任务,而线程池中全部是Background线程。

除了使用Thread类创建Foreground线程外,设置Thread.CurrentThread.IsBackground属性值可以让运行中的Background线程变为Foreground线程。但这种方式应该谨慎使用,主要原因在于执行该语句的线程可能由线程池进行管理,我们难以在应用程序中对该线程的行为和生命周期进行控制,也不应该这样做。假如该线程执行任务非关键任务,又耗时比较长,那将其IsBackground设置为false同样会阻碍进程的退出,也不符合使用线程池的原则。但如果有明确的意图需要这样做,唯一需要保证的是让线程的任务快速完成。使用完线程池中的线程后忘记重置IsBackground为true并不会导致任何问题,因为线程池会在重用线程时重置这个值。

四、控制线程正常退出

回到上面的示例代码,假如我们已经决定要使用Foreground线程,那需要做的就是给线程的执行代码一个退出条件,让它在恰当的时候优雅的停止,而非无休止的运行下去。可以设置一个变量指示主窗口是否正在退出,再由线程定期检查这个变量,决定是否结束。

 1 public partial class Form1 : Form
 2 {
 3     Thread worker = null;
 4     bool isClosing = false;
 5 
 6     public Form1()
 7     {
 8         InitializeComponent();
 9 
10         worker = new Thread(new ThreadStart(DoWork));
11         worker.Start();
12     }
13 
14     private void DoWork()
15     {
16         while (!isClosing)
17         {
18             Thread.Sleep(1000);
19             if (IsHandleCreated && !IsDisposed)
20                 Invoke((MethodInvoker)(() => label1.Text = DateTime.Now.ToString()));
21         }
22     }
23 
24     protected override void OnClosing(CancelEventArgs e)
25     {
26         base.OnClosing(e);
27         isClosing = true;
28     }
29 }

五、Foreground导致Windows Service进程延迟退出 

对于Windows Service程序来讲,Foreground线程仍然会阻止Service进程的退出,但是情况稍有不同。一段最简单的Service程序代码如下,服务启动代码写在OnStart方法中,创建了一个线程对象循环执行任务,OnStop方法会在服务停止时被调用,这里假设需要5秒钟时间运行资源清

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇C#下利用封包、拆包原理解决Socke.. 下一篇标志枚举以及位运算

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目