C#核心知识
委托
如何声明一个委托:通过 【delegate 返回值类型 委托名称】 的格式来定义
如何使用一个委托:使用new关键字,并传入和声明委托的构造相同的方法名,比如:new 委托名称(与委托的参数和返回值相同的一个方法名)
如何调用一个委托:将new关键字返回的实例调用Invoke方法,或者像调用一个方法一样调用它,就像方法一个调用都可以
什么是多播委托:顾名思义,多播委托可以注册多个实现了委托的方法。
如何使用多播委托:通过+=,-=注册,取消注册委托
预定义委托:通过Action,Func来实现,他们的区别在于Action没有返回值,Func有返回值,返回值在最后一个泛型里。
匿名函数经常和委托一起使用,语法:()=>{}
事件
事件是在委托的基础上做的一个安全的处理,避免直接调用委托,而是通过+=,-=这种方式去注册,取消注册委托。并且在内部实现了线程安全的操作。
如果要研究源码可以在SharpLab中看到用原始方法是如何实现了高级语法的。
多线程
什么是线程?
一个进程有多个线程,线程是操作系统中独立运行的最小单位。
为什么要用多线程?
希望能同时运行多个任务,提高效率。
什么是线程池?
是一组预先创建的线程,可以被重复利用来执行任务。
什么是线程安全?
多个线程同时访问共享资源时,对它的访问不会产生数据不一致的后果。
同步机制:用于协调多个线程访问的执行顺序去访问共享资源,避免数据不一致的问题。通过lock(obj){}的方式实现同步。
lock语法糖实际是对Monitor的一个封装。
原子操作:在执行任务时要么全部执行,要么全部不执行,确保了数据的一致性。 通过Interlocked类实现原子操作
并行操作:使用并行操作(PLinq)可以大幅提高效率,使用AsParallel方法可以确保并行操作的执行。
如果前台线程消亡,后台线程也要跟着消亡,Main函数就是一个前台线程。
如何创建一个线程?
new Thread(方法名称).Start(方法的参数)
{
IsBackground = ,
....... // 配置
}
Join方法可以让当前线程先执行完毕,确保后面的线程不会在它之前执行。
Interrupt可以中断线程的执行
信号量(Semaphore):可以理解为高速公路的闸口和车道数,第一个参数控制开启的闸口数,第二个参数控制车道数。
异步编程
异步不意味着多线程,单线程也可以是异步的。异步默认是使用线程池的,可以从线程ID中观察的到,每次的ID都不一样。
多线程适合CPU密集型的操作,适合长期运行的任务
异步适合IO密集型的操作,适合短暂的小任务,例如:服务器的后端接口;异步可以查看到线程执行的状态。
异步任务的创建:
1、Task.Run()
2、Task.Factory.StartNew()
3、new Task().Start();
特性:
1、将方法标记为async后,方可在方法中使用await,还有一种同步的写法但不推荐:InvokeTask().GetAwaiter().GetResult();
2、async+await会将方法包装为状态机
3、异步编程具有传染性
async void这种写法几乎只用于对事件的注册。
常见的阻塞情形:
1、Task.Wait() Task.Result如果任务没有完成,则会阻塞当前线程,容易造成死锁。
2、InvokeTask().GetAwaiter().GetResult(); 依旧是个阻塞写法
3、Thread.Sleep(),使用异步时应该为Task.Delay(),这种写法会立即释放当前线程
4、IO耗时的操作
如何开启多个异步任务?
Task.WhenAll(task),Task.WhenAny(task),不要在for循环中await。
如何取消异步任务?
使用CancellationToken实例的Cancel方法,同时配合IsCancellationRequested属性可以在任务中精确控制是否继续执行任务。
任务超时如何实现?
在创建CancellationTokenSource时传入超时时间,比如:new CancellationTokenSource(2000);
注意事项
需要注意的是每次需要手动释放CancellationTokenSource
??运算符,当左边的参数不为空则返回它,如果为空则返回右边的参数
有哪些同步机制?
SemaphoreSlim 推荐,用法简单
Channel
第三方库 - AsyncLock,比较复杂适用于大型项目
如何在在同步方法中调用异步?
1、应避免使用xxx.Wait(),xxx.Result,有可能导致死锁,且无法捕获异常。
2、应避免使用async void,无法捕获到内部异常,内部不加异常捕获会导致程序挂掉
3、InvokeTask().GetAwaiter().GetResult(); 可以捕获到内部异常。
4、SafeFireForget:async void SafeFireForget(Task task, Action? completed, Action<Exception> expection),第一个参数是异步执行的业务方法,第二个参数是异步任务完成后的回调委托,第三个参数是异步任务出现异常的回调委托
5、ContinueWith:Job().ContinueWith(xxxTask),在异步任务完成后调用它的ContinueWith方法,参数是对任务完成与否的处理方法。
GC
.NET中将引用对象分为三类,第0代,第1代,第2代对象,一般会优先进行第0代回收, 然后将未成功回收的放到第1代中,如果第1代对象再次未回收成功会放到第2代中,这样做可以减少回收的成本,提高效率。
在C#中,析构函数(也称为终结器)主要用于释放非托管资源(如文件句柄、数据库连接、操作系统句柄等)。
class ResourceWrapper
{~ResourceWrapper() // 析构函数{// 释放非托管资源CloseHandle(fileHandle);}
}
重要配置
在项目文件xxx.csproj中修改以下配置
工作站与服务器模式
工作站模式:适合内存占用小的程序和桌面应用,回收频率高,是单线程的。
服务器模式:适合内存占用大的程序,回收频率低,吞吐量高,是多线程的。
1、true - 服务器模式,false - 工作站模式
<ServerGarbageCollection>true</ServerGarbageCollection>
普通GC与后台GC
普通GC:会导致单次停顿时间变长(例如UI线程的卡死),但消耗的资源少,支持压缩处理。
后台GC:单次停顿时间短,但停顿的频率多,消耗的资源多,不支持压缩处理。
2、true - 启动后台GC,false - 普通GC
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>