一.NET Core理论基础
===================.Net Core===================
1)CLI
类似于java的jre
有2种完全不相干的称呼:
Common Language Infrastructure 通用中间架构
Command Line Interface 命令行接口
c#: int一定是4字节。我们不需要关注平台差异。
C语言 int=至少2字节:在不同平台上是不同的字节。我们需要注意差异去编写不同的代码。
基础类型、指令种类、模块、类,方法的结构,二进制的文件格式
C#、VB.NET、F#-->最终编译为统一的IL
二进制的文件格式: 针对不同的操作系统生成不同的格式,还是不同的格式呢? ==》选择了后者统一的格式
dll: PE
.net core需要提供一种容器(工具),确保所有os平台上都可以加载PE格式的dll文件.
.NET Core CLI工具: .net core 2.0才稳定,早期经历很多挫折。因为开始是用不同平台开发的。
不管是vs还是rider,都是通过这个CLI与.net core交互的。
2)CoreCLR // 公共语言运行时,c++编写,可以找成员方法,翻译为原生平台的字节码: 类型、反射、方法、成员、GC
1.中间代码解析
.net程序: 包含中间代码IL,因此可以跨平台。
原生程序(可调用os native接口,只能运行在特定平台)
2.中间代码的编译
JIT(IL语言的编译器)
3.保证类型安全
c语言: int* --> void*, 传递到其它地方的话,是不知道是啥类型的,甚至可以转化为long,发射管不可预料的后果。
c#: string-->object, 我们GetType得到类型。
4.异常处理:
传统是通过返回值,需要开发者费心确定每个函数的返回值。
c#则可以try catch
5.线程管理:
c#对原生线程对象进行包装,我们可以用一致的代码进行多线程开发。 托管线程。
6.GC
只能管理托管代码。
3)CoreFX // 使用c#开发, 减少CoreCLR开发压力
基础类型,c#编写--》我们使用的时间、控制台等库
分开存放: 共用、特定平台
条件编译 // 避免工厂模式的使用。
跨平台兼容的基础类库:partial
.NET Standard 2.0标准: .NET Framework 4.6.1 .NET Core 2.0
4)Roslyn // .net上的高级语言的编译平台(不仅提供了代码编译功能,还提供了代码分析,我们可以调用Roslyn的API进行平台编译)
我们可以把代码粘贴到文本框中,进行编译。
之前是: CSC。 Roslyn则是更加高效,提供了动态编译功能,敲完代码,Roslyn就可以告诉vs代码有哪些错误。
我们可以用扩展方法,自己添加类库的方式,而不需要去修改.net core的源码。
===================ASP .Net Core===================
ASP.NET Core 之前不依赖.NET Core
前身: ASP .NET MVC,早就开源了
MONO: 支持运行到linux
源码:
aspnetcore
runtime
.NET Standard:
它不是程序,而是纯文本的类型和函数声明信息。
规范:相同功能的类型和函数,在不同的.NET开发框架中具有相同的形态。
=============================
http://source.dot.net/#q=Directory
dnSpy
=============================
postman
.NET Standard: 是标准,可以被不同应用程序支持和运行的标准,只是个类库。 版本越高,兼容性越低。 一般是做类库时,可以创建这个。
NotSupportedException:
如: AppDomain.CreateDomain就是在Linux不支持。
.NET Core:
Core CLR 和 .NET Framework CLR区别大吗?
只是某个方法不支持。
==================
ABP=Spring
==========
1. .NET Portability Analyzer
迁移源代码工程的分析工具
http://github.com/microsoft/dotnet-apiport
2. .NET API 目录查询
http://apisof.net/catalog
3. .NET 在线源码
http://source.dot.net/
4. dnSpy
http://github.com/0xd4d/dnSpy
.net core和java运行性能对比
c#
using System.Diagnostics;
class Program
{
static void Main(string[] args)
{
while (true)
{
var list = new List();
var sw = Stopwatch.StartNew();
for (int i = 0; i < 100_000_000; i++)
{
list.Add(i);
list.RemoveAt(0);
}
Console.WriteLine(sw.Elapsed);
}
}
}
/*
Administrator@DESKTOP-JTMBOEI MINGW64 /d/2_test_java/ConsoleApp1/ConsoleApp1/bin/Release/net6.0/win-x64/publish
$ ./ConsoleApp1.exe
00:00:00.3990029
00:00:00.3877960
00:00:00.3774225
00:00:00.3772492
00:00:00.3771980
*/
java
package com.example.demo.testLoop;
import org.springframework.util.StopWatch;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
while (true) {
List list = new ArrayList();
long start = System.currentTimeMillis();
for (int i = 0; i < 100_000_000; i++) {
list.add(i);
list.remove(0);
}
System.out.println(System.currentTimeMillis() - start);
}
}
}
/*
739
739
620
626
617
617
617
620
618
617
619
624
629
615
625
*/
结论:
这个例子c#比java快了将近1倍。
同时占用更少的内存,支持跨平台。
所以做游戏用c#可能是更合适的。
四、异步操作
异步:表示执行某项操作后,不等待操作结束,但是可以在操作结束后收到通知。
Linux平台,一个线程栈默认会分配8~10M的空间。
C10K个客户端--》连接服务。
问题:
如果1W个客户端去连接服务器,那消耗的内存就过大!
解决办法:
事件循环机制: 1个或多个线程专门用于捕获对象状态,把执行的阻塞操作换为:非阻塞操作。 再注册时间以处理完成后收到通知。 如:node.js的异步。
但是基于这种机制,编写代码困难。
于是出现:基于回调的异步操作。 如:netty, iocp, aio.
但是c#本身就支持异步编程模型:APM, 带回调委托。
.net core再不同平台使用不同的方案:IOCP, epoll, kqueue。
以回调封装的不好。于是.net封装出了TPL任务并行库。 让任何的异步操作都具有相同的注册,等待结束,和进行处理,处理错误。 并为:await, async打下基础。
Task:表示执行一个没有任何返回类型的异步方法。 ContinueWith:可以在一个异步操作完成后,执行另外一个异步操作。
异步操作具有传染性。一个是异步,其它全部异步了。
一般在任务中使用委托,委托完成任务完成。 委托在.net运行内部的线程池中回调。
第二种是:Promise模式,一种是承诺对象(负责设置操作结果和发送异常),一种是Future将来对象(负责注册回调和接收承诺的结果)。
.net中封装了,其中:承诺接口是不对外公开的,我们只能对指定的类型进行操作。
ValueTask类型=Task的包装类=值类型。
async, await。
================
async, await // 也是基于TPL, 有了这2个后,我们很少再去写Task了,极大的简化的异步的编写。
async用于标记方法会使用await进行一步操作,把整个方法合并为一个异步操作,
非常直观的编写一连串的异步操作。
和我们阻塞操作+ 多线程是差不多的。
只不过多了一个关键字。
只不过阻塞操作需要更多的线程配合。
而异步操作可以在有限的线程中同时执行大量操作。
异步操作:需要多线程的支持,但是不需要很多个多线程,1W个任务,业务2个线程就行了,1个线程处理任务,1个线程检查状态并调用回调。
============实现====
C#编译器把方法内部的逻辑和本地的变量合并在一起作为一个状态机处理。
.net运行时提供了相关类型的支持,原有的方法体会变成创建状态机的代码。
多个异步操作合并为一个:状态机的话,默认state=-1,state=0表示第一个异步执行完成,state=1表示第2个异步执行完成。 state=-2表示整个异步执行完成。
MoveNext会执行到下一步。
最后还是反编译为Task了!
await关键字转化为了awaiter。 TaskAwaiter。
本质就是:每执行一步,记录一个状态。
=============
异步本地存储: AsyncLocal // 通过执行上下文保存, 每一个托管对象都有一个。
因为调用前的线程和返回到的线程不一定是一个线程。
============
执行子任务的时候是要保存上下文的。
==============
线程上下文的切换(唤醒/等待的切换 = 这会消耗资源),但是线程池中线程几乎都是唤醒着的。
除非线程池中线程不够。
===========
大部分情况都涉及IO,因此能用异步就用异步,最大化利用硬件资源, 不用担心乱用。
==========CPU上下文的切换
主动切换 // 自己时间到了
被动切换 // 时间片到了
休眠与唤醒的切换,是毫秒级。
2个线程都醒着,一个线程在重试,一个线程在运行,那切换几乎没有消耗。纳秒.
六、依赖注入
1)基础知识
.NET Core 新增的依赖注入。
IOC的作用:
1.映射依赖,注册类型,注册服务
2.实例解析
为什么要控制反转:
不需要去new类型了,因为每次new可能有风险,变为找第三方创建。
如果是负责的对象,需要依赖很多其它的对象。
抽象:接口=服务。
如:发消息的服务。 我只管发送消息就行, 如: 发送短信,微信,我只管引入:发送消息的服务就行。 我需要短信就注入短信,需要微信就注入微信,我们不需要去修改业务的代码。
为什么需要DI:
帮助管理复杂的依赖关系。
扩展性。
2)DI
内置的DI:
一个构造必须是另外一个的超集。
Sctutor(推荐:程序集注册+筛选): 不是依赖注入框架,.NET内置DI的扩展包,是为了弥补.NET 内置DI的服务注册方式。
底层依然是内置DI。 就算版本更新,Sctutor是针对DI进行抽象扩展。
Autofac: 第三方的依赖注入框架。
很多人为了注册方式。
3)整合第三方的设计: 托管主机系统, ASP .NET