0%

代码块的锁定

锁定某一代码块,让同一时间只有一个线程访问该代码块

被锁定对象不能保证不被外部线程修改

如下面代码所示,有一把锁,每十毫秒修改一次密码,而验证密码的时候,即使锁定了locker对象,依然不能改变locker密码被修改的命运。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
internal class Program
{
class Locker
{
public string key = "";
}
static void Main(string[] args)
{
Locker locker = new Locker();
TaskFactory factory = new TaskFactory();
factory.StartNew(() =>
{
while (true)
{
Thread.Sleep(10);
//修改密码
locker.key = Guid.NewGuid().ToString();
}
});
lock (locker)
{
string key = locker.key;
Thread.Sleep(100);
if (locker.key == key)
{
Console.WriteLine("密码正确");
}
else
{
Console.WriteLine("密码不正确");
}
}
}
}
>>>>>>>>>>>>>>>>>>>
密码不正确

如何改变这种情况呢,根据场景需求,可以在需要改变locker对象的时候,都去锁住该对象,所有线程提前约定好,只能有一个地方获取锁并修改它。毕竟锁的主人和小偷之间不会做这种约定。

将上述代码改变一下,主人就不能随时后台修改密码了,只能拿到锁后才能修改

1
2
3
4
5
6
7
8
9
10
while (true)
{
lock (locker)
{
Thread.Sleep(10);
locker.key = Guid.NewGuid().ToString();
}
}
>>>>>>>>>>>>>>>>>>
密码正确

内存结构

  • 对象类型指针(MethodTable方法表包含类型及其方法)可获得类型定义
  • 同步块索引占4字节,64位系统额外需要4字节对齐
  • 托管内存中对齐:32位系统4字节对齐,64位需要8字节对齐
  • 托管内存中智能排列字段使对象占用尽量少的内存
  • 托管对象无法使用代码获取其大小
  • StructLayout是用于封送非托管对象时使用,不影响托管内存中的排布方式

托管内存中的占用情况

1
2
3
4
5
6
7
8
class()
{
int number; //4 bytes
char c; //2 bytes
double d; //8 bytes
float f; //4 bytes
bool b; //1 byte
}
1
2
3
4
5
6
7
8
9
10
11
---------------------------------------------------
| Method Table Pointer | 8 bytes
|-------------------------------------------------|
| Synchronization Block Index | 8 bytes
|-------------------------------------------------|
| double | 8 bytes
|-------------------------------------------------|
| int | float | 8 bytes
|-------------------------------------------------|
| bool| char | | 8 bytes
---------------------------------------------------

非托管内存排布

  • 此对齐方式只是针对于在输出为非托管对象时的字段排列方式,在托管内存中不生效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//
// 摘要:
// Controls the layout of an object when exported to unmanaged code.
public enum LayoutKind
{
//
// 摘要:
// The members of the object are laid out sequentially, in the order in which they
// appear when exported to unmanaged memory. The members are laid out according
// to the packing specified in System.Runtime.InteropServices.StructLayoutAttribute.Pack,
// and can be noncontiguous.
Sequential = 0,
//
// 摘要:
// The precise position of each member of an object in unmanaged memory is explicitly
// controlled, subject to the setting of the System.Runtime.InteropServices.StructLayoutAttribute.Pack
// field. Each member must use the System.Runtime.InteropServices.FieldOffsetAttribute
// to indicate the position of that field within the type.
Explicit = 2,
//
// 摘要:
// The runtime automatically chooses an appropriate layout for the members of an
// object in unmanaged memory. Objects defined with this enumeration member cannot
// be exposed outside of managed code. Attempting to do so generates an exception.
Auto = 3
}

Sqquential 顺序布局

  • CLR对struct的Layout的处理方法与C/C++中默认的处理方式相同,即按照结构中占用空间最大的成员进行对齐(Align)
1
2
3
4
5
6
7
8
9
10
[StructLayout(LayoutKind.Sequential)]
class Sample()
{
int number; //4 bytes
char c; //2 bytes
double d; //8 bytes
float f; //4 bytes
bool b; //1 byte
}
//sizeof(Sample) = 40个字节

Explicit 精准布局

  • CLR不对结构体进行任何内存对齐(Align)
  • 需要用FieldOffset()设置每个成员的位置
1
2
3
4
5
6
7
8
9
10
[StructLayout(LayoutKind.Explicit)]
class Sample()
{
[FieldOffset(0)]
int number1;
[FieldOffset(0)]//公用体
int number2;
[FieldOffset(4)]
int number2;
}

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

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

  1. 只能提交二进制,而且只能提交一个二进制,如果提交文件的话,只能提交一个文件,后台接收参数只能有一个,而且只能是流(或者字节数组)。
  2. 属于HTTP规范中Content-Type的一种。

application/x-www-form-urlencoded

  1. 不属于http content-type规范,通常用于浏览器表单提交,数据组织格式:name1=value1&name2=value2,post时会放入http body,get时,显示在在地址栏。
  2. 当actionw为post时候,表单数据被编码为名称/值对。这是标准的编码格式。(在Form元素的语法中,EncType表明提交数据的格式 用 Enctype 属性指定将数据回发到服务器时浏览器使用的编码类型。)
  3. 当action为get时候,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2…),然后把这个字串append到url后面,用?分割,加载这个新的url。(注意是浏览器自动的操作,实际编码中不可以。)

multipart/form-data

  1. 既可以提交普通键值对,也可以提交(多个)文件键值对。
  2. HTTP规范中的Content-Type不包含此类型,只能用在POST提交方式下,属于http客户端(浏览器、java httpclient)的扩展。
  3. 通常在浏览器表单中,或者http客户端(java httpclient)中使用。
  4. 格式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST http://www.icyrene.cn/
Host: 192.168.0.201:8694
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="name1"

value1
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="name2"

value2
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file1"; filename="94b5b232gw1ewlx3p595wg20ak0574qq.gif"
Content-Type: image/gif


----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="file2"; filename="1443175219259.jpg"
Content-Type: image/jpeg


----WebKitFormBoundary7MA4YWxkTrZu0gW

程序运行时的内存结构

程序运行时分为三个部分

  • 托管堆--------用来存放引用类型对象
  • 调用堆栈------用来存放有序的调用方法及值类型和指针
  • 计算堆栈------指令运算时的堆栈

IL语言中的调用堆栈过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Program
{
static void Main(string[] args)
{
int x = 2;
int y = 3;
int z = Add(x, y);
}
static int Add(int a,int b)
{
int sum;
sum = a + b;
return sum;
}
}

IL代码如下所示
images
接下来逐步解析

步骤序号 指令 描述
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 返回
images

结构类型

结构类型是值类型,存储在栈中

如下图所示,右边托管堆中的快照分别为左侧断点的各个时期的快照,表明结构从
images

使用ref关键字按引用方式传递结构类型变量

将结构类型变量作为参数传递给方法或从方法返回结构类型值时,将复制结构类型的整个实例。 这可能会影响高性能方案中涉及大型结构类型的代码的性能。 通过按引用传递结构类型变量,可以避免值复制操作。 使用 ref、out 或 in 方法参数修饰符,指示必须按引用传递参数。 使用 ref 返回值按引用返回方法结果。

引用结构

从 C# 7.2 开始,可以在结构类型的声明中使用 ref 修饰符。 ref 结构类型的实例在堆栈上分配,并且不能转义到托管堆。 为了确保这一点,编译器将 ref 结构类型的使用限制如下:

  • 引用结构不能是数组的元素类型。
  • 引用结构不能是类或非 ref 结构的字段的声明类型。
  • 引用结构不能实现接口。
  • 引用结构不能被装箱为 System.ValueType 或 System.Object。
  • 引用结构不能是类型参数。
  • 引用结构变量不能由 lambda 表达式或本地函数捕获。
  • 引用结构变量不能在 async 方法中使用。 但是,可以在同步方法中使用 ref 结构变量,例如,在返回 Task 或 Task 的方法中。
  • 引用结构变量不能在迭代器中使用。

内存管理

  • 值类型存储在栈(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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class UnManaged:IDisposable
{
private bool disposedValue;

public UnManaged()
{

}

protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: 释放托管状态(托管对象)
}

// TODO: 释放未托管的资源(未托管的对象)并替代终结器
// TODO: 将大型字段设置为 null
disposedValue = true;
}
}

// // TODO: 仅当“Dispose(bool disposing)”拥有用于释放未托管资源的代码时才替代终结器
// ~UnManaged()
// {
// // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
// Dispose(disposing: false);
// }

public void Dispose()
{
// 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}

什么是委托

就是一个能存放很多方法的指针的调用清单(但方法签名必须和委托类型签名一样),你一调用这个清单,那么清单里的所有的指针所对应的方法就会依次被执行,注意是很多方法。

委托的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//委托的声明
public delegate void SampleDele(string str);
//定义与委托签名相同的三个方法
public static void dele1(string str)
{
Console.WriteLine("dele1:" + str);
}
public static void dele2(string str)
{
Console.WriteLine("dele2:" + str);
}
public static void dele3(string str)
{
Console.WriteLine("dele3:" + str);
}
//
static void Main(string[] args)
{
SampleDele dele = new SampleDele(dele1);
dele += dele2;
dele += dele3;
dele.Invoke("arg");
}

运行结果如下图所示
images

string比较

  • String 是引用类型
  • Object.Equals(Object)比较原理是比较的对象的引用
  • == 的方法等于Equals,但是String重写了这个方法,实则比较的是值

string的两种创建方式创建方式

1
string str1 = "string 1";

堆中情况为
images

1
String str2 = new String("string 2");

堆中的情况如下入
images

string的截取

在 .NET Core 2.1 开始支持新方式,这种方式取出字符串替代了 SubString 这种会额外生成临时字符串的方式。如果上述代码发生在较大或较多文本的处理中,那么反复的拼接将生成大量的临时字符串,造成大量 GC 压力;而使用 Span 将不会额外生成任何临时字符串。

1
2
3
4
//原始写法
var sub = "strings".SubString(0,1);
//新写法
var sub = "strings".AsSpan(0,1);

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  同步方法 !!“;

    });