上一篇文章介绍了句柄的基本概念,也描述了C#中创建文件句柄的过程。我们已经知道句柄代表Windows内部对象,文件对象就是其中一种,但显然系统中还有更多其它类型的对象。本文将简单介绍Windows对象的分类。

句柄可以代表的Windows对象分为三类,内核对象(Kernel Object)、用户对象(GDI Object)和GDI对象,上一篇文章中任务管理器中的“句柄数”、“用户对象”和“GDI对象”计数就是与这几类对象对应的。为什么要这样分类呢?原因就在于这几类对象对于操作系统而言有不同的作用,管理和引用的方式也不同。内核对象主要用于内存管理、进程执行以及进程间通信,用户对象用于系统的窗口管理,而GDI对象用来支持图形界面。

一、观察句柄变化的小实验

在列举Windows对象的分类之前,我们再看一个关于句柄数量的实验,与之前文件对象的句柄不同,本例中的句柄属于用户对象。程序运行过程中,对象的创建和销毁是动态进行的,句柄数量也随之动态变化,即使是一个最简单的Windows Form程序也可以直观的反映这一点。下图是一个只有文本框和按钮的窗体程序,程序启动后默认输入焦点在文本框上,可以按下Tab键将焦点在文本框和按钮之间交替切换。当我们这样做时,在任务管理器中可以看到:用户对象的数量在21和20之间不断变化。这一数字在你的运行环境中可能不同,但至少说明在焦点切换过程中有一个用户对象在不断的被创建销毁,这个对象就是Caret(插入符号)。

Caret是用户对象的一种,这个闪烁的光标指示输入的位置。我们可以通过Windows API创建这个符号,定制它的样式,也可以设置闪烁时间。创建Caret时,Windows API并不返回它的句柄,原因是一个窗口只能显示一个插入符号,可以通过窗口的句柄对它进行访问,或者更简单的,看哪个线程在调用这些API即可。但无论如何,Caret对象和其句柄是真实存在的,即便我们不需要获取这个句柄。

二、Windows对象的分类

前面提到了Windows对象分为内核对象、用户对象和GDI对象,也举了文件对象和Caret对象的例子,除此之外还有很多其它类型的对象。Windows对象的完整列表,可以参考MSDN中关于Object Categories (Windows) 的描述,其中列举了每个类别的对象,并且针对每种对象都有详细的说明,你可以从中找到这些对象的用法,和对应的Windows API等。本文主要讨论.NET对象和Windows对象的关系,因此在这里只简单列举这些对象以供快速参考。

内核对象:访问令牌、更改通知、通信设备、控制台输入、控制台屏幕缓冲区、桌面、事件、事件日志、文件、文件映射、堆、作业、邮件槽、模块、互斥量、管道、进程、信号量、套接字、线程、定时器、定时器队列、定时器队列定时器、更新资源和窗口站。

用户对象:加速键表、插入符号、光标、动态数据交换会话、钩子、图标、菜单、窗口和窗口位置。

GDI对象:位图、画刷、设备上下文、增强型图元文件、增强型图元文件设备上下文、字体、内存设备上下文、图元文件、图元文件设备上下文、调色板、画笔和区域。

如前所述,不同类别的对象具有不同的作用和特点。内核对象主要用于内存管理、进程执行以及进程间通信。多个进程可以共用同一个内核对象(如文件和事件),但每个进程必须独自创建或打开这个对象以获取自己的句柄,并指定不同的访问权限,这种情况下,一个内核对象会被多个进程的句柄引用;用户对象用于系统的窗口管理,与内核对象不同的是,一个用户对象仅能有一个句柄,但句柄是对其它进程公开的,因此其它进程可以获取并使用这个句柄来访问用户对象。以窗口(Windows)对象为例,一个进程可以获取另一个进程创建的窗口对象的句柄,并向其发送各种消息,这也是很多自动化测试工具得以实现的前提;而GDI对象用来支持图形界面,也只支持单个对象单个句柄,但与用户对象不同的是,GDI对象的句柄是进程私有的。

三、与Windows对象对应的.NET对象

.NET中有不少类型封装了上面所列举Windows对象,我们在使用时要特别注意对这些对象的进行重用和适时销毁。下表是一些对应关系的例子(注意这不是完整列表,也并非严格的一一对应关系),后续文章将会讨论其中一些重要类型的用法。

.NET对象

引用到的Windows对象句柄

分类

System.Threading.Tasks.Task

访问令牌

内核对象

System.IO.FileSystemWatcher

更改通知

内核对象

System.IO.FileStream

文件

内核对象

System.Threading.AutoResetEvent
System.Threading.ManualResetEvent
System.Xaml.XamlBackgroundReader

事件

内核对象

System.Diagnostics.EventLog

事件日志

内核对象

System.Threading.Thread

线程

内核对象

System.Threading.Mutex

互斥量

内核对象

System.Threading.Semaphore

信号量

内核对象

System.Windows.Forms.Cursor

光标

用户对象

System.Drawing.Icon

图标

用户对象

System.Windows.Forms.Menu

菜单

用户对象

System.Windows.Forms.Control

窗口

用户对象

System.Windows.Forms.Control
System.Drawing.BufferedGraphicsManager
System.Drawing.Bitmap

位图

GDI对象

System.Drawing.SolidBrush
System.Drawing.TextureBrush

画刷

GDI对象

System.Drawing.Font

字体

GDI对象

四、.NET中与句柄泄露相关的异常和现象

上一篇文章提到了句柄的限制,当进程或系统的句柄数量达到上限时,程序运行就会出现异常。常见的错误是System.ComponentModel.Win32Exception的“Error creating window handle”,或者“存储空间不足,无法处理此命令”等,错误出现时内存往往也会有显著增长。如果是达到了系统级别的句柄上限,其它程序的运行也受到影响,系统可能无法打开任何新的菜单和窗口、窗口也会出现绘制不完整的情况。这时及时抓取Dump并终止泄露句柄的进程,系统往往立即恢复正常。

五、第一个句柄泄露的例子

下面的示例代码包含句柄泄露的问题,为了演示方便,实现代码被最简单化,设计的合理性也暂且不作深究。代码模拟了一个应用场景:程序包含一个DataReceiver不断从某个数据源获取实时数据,DataReceiver同时会启动一个DataAnalyzer,定时分析这些数据。设想程序有一个专门的子窗口来显示这些数据,当子窗口被临时关闭时,数据的实时获取和分析过程也可以暂时终止。程序长时间运行的过程中,子窗口可能被用户多次关闭和打开,因此DataReceiver会被创建多次,程序启动后的代码模拟DataReceiver被创建和Dispose了1000次。

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Timer = System.Threading.Timer; namespace LeakExample
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent(); // 模拟程序运行过程中多次创建DataReceiver的情况
Task.Factory.StartNew(() => {
for (int i = 0; i < 1000; i++)
{
using (IDisposable receiver = new DataReceiver())
{
Thread.Sleep(100);
}
}
});
}
} public class DataReceiver : IDisposable
{
private Timer dataSyncTimer = null;
private IAnalyzer analyzer = null;
private bool isDisposed = false; public DataReceiver() : this(new DataAnalyzer()) { } public DataReceiver(IAnalyzer dataAnalyzer)
{
dataSyncTimer = new Timer(GetData, null, 0, 500);
analyzer = dataAnalyzer; analyzer.Start();
} private void GetData(object state)
{
// 获取数据并放入缓存
} public void Dispose()
{
if (isDisposed)
return; if (dataSyncTimer != null)
{
dataSyncTimer.Dispose();
} isDisposed = true;
}
} public interface IAnalyzer
{
void Start();
void Stop();
} public class DataAnalyzer : IAnalyzer
{
private Timer analyzeTimer = null; public void Start()
{
analyzeTimer = new Timer(DoAnalyze, null, 0, 1000);
} public void Stop()
{
if (analyzeTimer != null)
{
analyzeTimer.Dispose();
}
} private void DoAnalyze(object state)
{
// 从缓存中取得数据并分析,耗时600毫秒
Thread.Sleep(600);
}
}
}

当运行这段程序时,可以从任务管理器观察到句柄数持续增长,最终基本稳定在某一个较高的数字。虽然DataReceiver被多次创建,但句柄数的增长最终远远超过其被创建的次数。由于代码简单,你很可能已经看出问题所在,然而在实际的项目中,由于软件架构和业务逻辑代码更为复杂,很难一眼就看出问题的根源。下一篇文章将从这个例子入手,结合一些工具来分析问题存在的原因,并讨论Timer是如何工作的。

.NET对象与Windows句柄(二):句柄分类和.NET句柄泄露的例子的更多相关文章

  1. .NET对象与Windows句柄(三):句柄泄露实例分析

    在上篇文章.NET对象与Windows句柄(二):句柄分类和.NET句柄泄露的例子中,我们有一个句柄泄露的例子.例子中多次创建和Dispose了DataReceiver和DataAnalyzer对象, ...

  2. .NET对象与Windows句柄(一):句柄的基本概念

    在.NET编程中,得益于有效的内存管理机制,对象的创建和使用比较方便,大多数情况下我们无须关心对象创建和分配内存的细节,也可以放心的把对象的清理交给自动垃圾回收来完成.由于.NET类库对系统底层对象进 ...

  3. 【C# 线程】转载 句柄的基本概念 .NET对象与Windows句柄

    转载自:https://www.cnblogs.com/silverb/p/5300255.html 句柄的基本概念 1.句柄就是进程句柄表中的索引.2.句柄是对进程范围内一个内核对象地址的引用,一个 ...

  4. Delphi对象变成Windows控件的前世今生(关键是设置句柄和回调函数)goodx

    ----------------------------------------------------------------------第一步,准备工作:预定义一个全局Win控件变量,以及一个精简 ...

  5. Windows Forms(二)

    导读 1.用VS创建一个Windows Forms程序 2.分析上面的程序 3.Mediator pattern(中介者模式) 4.卡UI怎么办——BackgroundWorker组件 用VS创建一个 ...

  6. 从普通函数到对象方法 ------Windows窗口过程的面向对象封装

    原文地址:http://blog.csdn.net/linzhengqun/article/details/1451088 从普通函数到对象方法 ------Windows窗口过程的面向对象封装 开始 ...

  7. PHP 类与对象 全解析( 二)

    目录 PHP 类与对象 全解析( 一) PHP 类与对象 全解析( 二) PHP 类与对象 全解析(三 ) 7.Static关键字 声明类成员或方法为static,就可以不实例化类而直接访问.不能通过 ...

  8. C++ 在Windows下截取整个屏幕 和 指定句柄窗口的屏幕

    #include <windows.h> #include <stdint.h> #include <stdio.h> void ShootScreen(const ...

  9. XAML实例教程系列 - 对象和属性(二)

    XAML实例教程系列 - 对象和属性 2012-05-22 14:18 by jv9, 1778 阅读, 6 评论, 收藏, 编辑 在前一篇已经介绍XAML概念:“XAML语言是Extensible ...

随机推荐

  1. 餐厅到店点餐系统app燃尽图

    队友: 郭志豪:http://www.cnblogs.com/gzh13692021053/ 杨子健:http://www.cnblogs.com/yzj666/ 刘森松:http://www.cnb ...

  2. 错误:不存在类型或命名空间名称 "Control”

    编译时出现如下错误: 命名空间中不存在类型或命名空间名称 "Control"(是否缺少程序集引用?).     在引用中添加UIAutomationProvider后解决.

  3. BZOJ2862 : 分糖果

    二分答案$x$表示最大的一段的和. 设$f[i]$表示前$i$个最多分几段,满足最大的一段不超过$x$,若$f[n]\geq k$,则可行, 则$f[i]=\max(f[j])+1,sum[i]-su ...

  4. JavaScript 变量、函数与原型链

    定义 || 赋值 1-函数的定义 函数定义的两种方式: “定义式”函数:function fn(){ alert("哟,哟!"); } “赋值式”函数:var fn = funct ...

  5. MONO 架构

  6. Float Equal Problem

    Understand limitations of floating point representations.Never check for equality with ==. Instead, ...

  7. BZOJ4143 [AMPPZ2014]The Lawyer

    Description Byteasar要制订m天的会议计划,一共有n场会议,第i场会议开始于第d[i]天的第a[i]秒,结束于第d[i]天的第b[i]秒. 对于每一天,请找出这一天的两场会议i,j, ...

  8. Qt Examples Qt实例汇总

    ActiveQt Examples Using ActiveX from Qt applications. Animation Framework Examples Doing animations ...

  9. CentOS网卡配置文件

    [root@xaiofan ~]# cat /etc/sysconfig/network-scripts/ifcfg-eth0 DEVICE=eth0TYPE=EthernetONBOOT=yesNM ...

  10. 矢量图标转成16*16大小的SVG格式

    著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处.作者:一一链接:http://www.zhihu.com/question/32233782/answer/68629385来源:知 ...