CSharp之锁(Lock)
代码块的锁定
锁定某一代码块,让同一时间只有一个线程访问该代码块
被锁定对象不能保证不被外部线程修改
如下面代码所示,有一把锁,每十毫秒修改一次密码,而验证密码的时候,即使锁定了locker对象,依然不能改变locker密码被修改的命运。
1 | internal class Program |
如何改变这种情况呢,根据场景需求,可以在需要改变locker对象的时候,都去锁住该对象,所有线程提前约定好,只能有一个地方获取锁并修改它。毕竟锁的主人和小偷之间不会做这种约定。
将上述代码改变一下,主人就不能随时后台修改密码了,只能拿到锁后才能修改
1 | while (true) |
CSharp对象的内存结构
内存结构
- 对象类型指针(MethodTable方法表包含类型及其方法)可获得类型定义
- 同步块索引占4字节,64位系统额外需要4字节对齐
- 托管内存中对齐:32位系统4字节对齐,64位需要8字节对齐
- 托管内存中智能排列字段使对象占用尽量少的内存
- 托管对象无法使用代码获取其大小
- StructLayout是用于封送非托管对象时使用,不影响托管内存中的排布方式
托管内存中的占用情况
1 | class() |
1 | --------------------------------------------------- |
非托管内存排布
- 此对齐方式只是针对于在输出为非托管对象时的字段排列方式,在托管内存中不生效
1 | // |
Sqquential 顺序布局
- CLR对struct的Layout的处理方法与C/C++中默认的处理方式相同,即按照结构中占用空间最大的成员进行对齐(Align)
1 | [StructLayout(LayoutKind.Sequential)] |
Explicit 精准布局
- CLR不对结构体进行任何内存对齐(Align)
- 需要用FieldOffset()设置每个成员的位置
1 | [StructLayout(LayoutKind.Explicit)] |
Auto 自动布局
- CLR会对结构体中的字段顺序进行调整,使实例占有尽可能少的内存,并进行4byte的内存对齐(Align)
StructLayout特性三种附加字段
Charset
- CharSet定义在结构中的字符串成员在结构被传给DLL时的排列方式。可以是Unicode、Ansi或Auto。
- 默认为Auto,在WIN NT/2000/XP中表示字符串按照Unicode字符串进行排列,在WIN 95/98/Me中则表示按照ANSI字符串进行排列。
Pack
- Pack定义了结构的封装大小。可以是1、2、4、8、16、32、64、128或特殊值0。特殊值0表示当前操作平台默认的压缩大小。
Http协议
Http
TCP套接字传输
超时
请求超时(未连接到服务器)
- 网络不好
- 等待时长为5秒
响应超时(连接到服务器)
- 服务端连接后挂掉
- 服务端处理响应时间过长
- TimeOut: 30 (单位秒)
Connection
Http协议建立在TCP之上,若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;
- connection: keep-alive (长连接HTTP 1.1 默认值)
- connection: close (短连接HTTP 1.0默认值)
- keep-alive: timeout=20 (长连接TCP通道保持20秒)
- keep-alive: max=10 (长连接TCP通道保持10次请求便断开)
Http Request
- 请求行
- 请求头
- 空行(不可省)
- 请求体(可无)
Http Response
- 状态行
- 消息报头
- 空行
- 响应体
Content-Type
MediaType,即是Internet Media Type,互联网媒体类型,在Http协议消息头中,使用Content-Type来表示请求体中的媒体类型信息。
格式
类型格式:type/subtype(;parameter)? type
常见类型
标识 | 类型 | 描述 |
---|---|---|
text/html | HTML格式 | |
text/plain | 纯文本格式 | |
text/xml | XML格式 | |
image/gif | gif图片格式 | |
image/jpeg | jpg图片格式 | |
image/png | png图片格式 | |
application/xhtml+xml | XHTML格式 | |
application/xml | XML数据格式 | |
application/atom+xml | Atom XML聚合格式 | |
application/json | ||
application/pdf | ||
application/msword |
application/octet-stream
- 只能提交二进制,而且只能提交一个二进制,如果提交文件的话,只能提交一个文件,后台接收参数只能有一个,而且只能是流(或者字节数组)。
- 属于HTTP规范中Content-Type的一种。
application/x-www-form-urlencoded
- 不属于http content-type规范,通常用于浏览器表单提交,数据组织格式:name1=value1&name2=value2,post时会放入http body,get时,显示在在地址栏。
- 当actionw为post时候,表单数据被编码为名称/值对。这是标准的编码格式。(在Form元素的语法中,EncType表明提交数据的格式 用 Enctype 属性指定将数据回发到服务器时浏览器使用的编码类型。)
- 当action为get时候,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2…),然后把这个字串append到url后面,用?分割,加载这个新的url。(注意是浏览器自动的操作,实际编码中不可以。)
multipart/form-data
- 既可以提交普通键值对,也可以提交(多个)文件键值对。
- HTTP规范中的Content-Type不包含此类型,只能用在POST提交方式下,属于http客户端(浏览器、java httpclient)的扩展。
- 通常在浏览器表单中,或者http客户端(java httpclient)中使用。
- 格式
1 | POST http://www.icyrene.cn/ |
CSharp之堆栈
程序运行时的内存结构
程序运行时分为三个部分
- 托管堆--------用来存放引用类型对象
- 调用堆栈------用来存放有序的调用方法及值类型和指针
- 计算堆栈------指令运算时的堆栈
IL语言中的调用堆栈过程
1 | class Program |
IL代码如下所示
接下来逐步解析
步骤序号 | 指令 | 描述 |
---|---|---|
IL_0000 | nop | 不作任何处理 |
IL_0001 | ldc.i4.2 | 在计算堆栈上开辟4位空间存放数植2 |
IL_0002 | stloc.0 | 将计算堆栈上的值推送到调用栈的参数0位置 |
IL_0003 | ldc.i4.3 | 在计算堆栈上开辟4位空间存放数植3 |
IL_0004 | stloc.1 | 将计算堆栈上的值推送到调用栈的参数1位置 |
IL_0005 | ldloc.0 | 将调用栈上0位置的值复制到计算堆栈 |
IL_0006 | ldloc.1 | 将调用栈上0位置的值复制到计算堆栈 |
IL_0007 | call | 使计算堆栈上的值作为参数调用相加方法 |
IL_000c | stloc.2 | 将计算堆栈上的值推动到调用栈的参数2位置 |
IL_000d | ret | 返回 |
CSharp之结构
结构类型
结构类型是值类型,存储在栈中
如下图所示,右边托管堆中的快照分别为左侧断点的各个时期的快照,表明结构从
使用ref关键字按引用方式传递结构类型变量
将结构类型变量作为参数传递给方法或从方法返回结构类型值时,将复制结构类型的整个实例。 这可能会影响高性能方案中涉及大型结构类型的代码的性能。 通过按引用传递结构类型变量,可以避免值复制操作。 使用 ref、out 或 in 方法参数修饰符,指示必须按引用传递参数。 使用 ref 返回值按引用返回方法结果。
引用结构
从 C# 7.2 开始,可以在结构类型的声明中使用 ref 修饰符。 ref 结构类型的实例在堆栈上分配,并且不能转义到托管堆。 为了确保这一点,编译器将 ref 结构类型的使用限制如下:
- 引用结构不能是数组的元素类型。
- 引用结构不能是类或非 ref 结构的字段的声明类型。
- 引用结构不能实现接口。
- 引用结构不能被装箱为 System.ValueType 或 System.Object。
- 引用结构不能是类型参数。
- 引用结构变量不能由 lambda 表达式或本地函数捕获。
- 引用结构变量不能在 async 方法中使用。 但是,可以在同步方法中使用 ref 结构变量,例如,在返回 Task 或 Task
的方法中。 - 引用结构变量不能在迭代器中使用。
CSharp之垃圾回收机制(GC)
内存管理
- 值类型存储在栈(Stack)中
- 引用类型的指针存储在栈中,实际对象存储在堆(Heap)中,指针的值为堆中对象的地址
对象的生命周期
当使用new关键字创建一个对象的时候,会在堆中开辟一个空间来存储这个对象,并且在栈中存储了一个指针,其值指向堆中的对象地址,当运行时的空间不足时,运行时会调用GC.Collect()方法进行资源回收,如果此时这个对象没有被引用了,则会被回收。
含有析构函数的非托管资源
非托管资源指的是.NET不知道如何回收的资源,最常见的一类非托管资源是包装操作系统资源的对象,例如文件,窗口,网络连接,数据库连接,画刷,图标等。这类资源,垃圾回收器在清理的时候会调用Object.Finalize()方法。默认情况下,方法是空的,对于非托管对象,需要在此方法中编写回收非托管资源的代码,以便垃圾回收器正确回收资源。
在.NET中,Object.Finalize()方法是无法重载的,编译器是根据类的析构函数来自动生成Object.Finalize()方法的,所以对于包含非托管资源的类,可以将释放非托管资源的代码放在析构函数。
不能在析构函数中释放托管资源,因为析构函数是有垃圾回收器调用的,可能在析构函数调用之前,类包含的托管资源已经被回收了,从而导致无法预知的结果。
Finalization Queue和Freachable Queue
这两个队列和.net对象所提供的Finalize方法有关。这两个队列并不用于存储真正的对象,而是存储一组指向对象的指针。当程序中使用了new操作符在Managed Heap上分配空间时,GC会对其进行分析,如果该对象含有Finalize方法则在Finalization Queue中添加一个指向该对象的指针。在GC被启动以后,经过Mark阶段分辨出哪些是垃圾。再在垃圾中搜索,如果发现垃圾中有被Finalization Queue中的指针所指向的对象,则将这个对象从垃圾中分离出来,并将指向它的指针移动到Freachable Queue中。这个过程被称为是对象的复生(Resurrection),本来死去的对象就这样被救活了。为什么要救活它呢?因为这个对象的Finalize方法还没有被执行,所以不能让它死去。Freachable Queue平时不做什么事,但是一旦里面被添加了指针之后,它就会去触发所指对象的Finalize方法执行,之后将这个指针从队列中剔除,这是对象就可以安静的死去了。.net framework的System.GC类提供了控制Finalize的两个方法,ReRegisterForFinalize和SuppressFinalize。前者是请求系统完成对象的Finalize方法,后者是请求系统不要完成对象的Finalize方法。ReRegisterForFinalize方法其实就是将指向对象的指针重新添加到Finalization Queue中。这就出现了一个很有趣的现象,因为在Finalization Queue中的对象可以复生,如果在对象的Finalize方法中调用ReRegisterForFinalize方法,这样就形成了一个在堆上永远不会死去的对象,像凤凰涅槃一样每次死的时候都可以复生。
IDisposable接口
GC并不是实时性的,这会造成系统性能上的瓶颈和不确定性。所以有了IDisposable接口,IDisposable接口定义了Dispose方法,这个方法用来供程序员显式调用以释放非托管资源。使用using 语句可以简化资源管理。
1 | class UnManaged:IDisposable |
CSharp之委托及原理
什么是委托
就是一个能存放很多方法的指针的调用清单(但方法签名必须和委托类型签名一样),你一调用这个清单,那么清单里的所有的指针所对应的方法就会依次被执行,注意是很多方法。
委托的用法
1 | //委托的声明 |
运行结果如下图所示
CSharp基础知识点之String
string比较
- String 是引用类型
- Object.Equals(Object)比较原理是比较的对象的引用
- == 的方法等于Equals,但是String重写了这个方法,实则比较的是值
string的两种创建方式创建方式
1 | string str1 = "string 1"; |
堆中情况为
1 | String str2 = new String("string 2"); |
堆中的情况如下入
string的截取
在 .NET Core 2.1 开始支持新方式,这种方式取出字符串替代了 SubString 这种会额外生成临时字符串的方式。如果上述代码发生在较大或较多文本的处理中,那么反复的拼接将生成大量的临时字符串,造成大量 GC 压力;而使用 Span
1 | //原始写法 |
WPF基础之调度对象
DispatcherObject
在 WPF 中绝大部分控件都继承自 DispatcherObject,甚至包括 Application。这些继承自 DispatcherObject 的对象具有线程关联特征,也就意味着只有创建这些对象实例,且包含了 Dispatcher 的线程(通常指默认 UI 线程)才能直接对其进行更新操作。
DispatcherObject 类有两个主要职责:提供对对象所关联的当前 Dispatcher 的访问权限,以及提供方法以检查 (CheckAccess) 和验证 (VerifyAccess) 某个线程是否有权访问对象(派生于 DispatcherObject)。
CheckAccess 与 VerifyAccess 的区别在于 CheckAccess 返回一个布尔值,表示当前线程是否可以使用对象,而 VerifyAccess 则在线程无权访问对象的情况下引发异常。通过提供这些基本的功能,所有 WPF 对象都支持对是否可在特定线程(特别是 UI 线程)上使用它们加以确定。如下图。
this.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate()
{
lblHello.Content = ”欢迎你光临WPF的世界,Dispatche 同步方法 !!“;
});