一 线程
线程池好处:ThreadPool 池化线程,避免频繁的申请和释放消耗资源;之前Thread每次都要申请和释放。
BackgroundWorker 这种方式比较适应于窗体应用程序。
/// <summary>
/// Parallel 是对Task的进一步封装,但会阻塞主线程,主线程会参与业务逻辑处理
/// </summary>
public class ParallelImplement
{
public void TestParallel()
{
//分配线程执行业务逻辑, Invoke可传多个业务处理,内部会自动分配线程处理
Parallel.Invoke(TestAction, TestAction2);
}
private void TestAction()
{
//这里实现线程处理的相关业务
Console.WriteLine($"子线程Thread({Thread.CurrentThread.Name})执行相关业务操作....{Thread.CurrentThread.ManagedThreadId}");
}
private void TestAction2()
{
//这里实现线程处理的相关业务
Console.WriteLine($"子线程Thread3333:({Thread.CurrentThread.Name})执行相关业务操作....{Thread.CurrentThread.ManagedThreadId}");
}
}
根据运行结果得出结论,主线程参与业务逻辑处理中,会阻塞线程, Parallel可根据业务进行选择使用。
开始了主线程id:1
子线程Thread()执行相关业务操作....1
子线程Thread3333:()执行相关业务操作....3
结束了:主线程id:1
参考:www.cnblogs.com/zoe-zyq/p/1…
1.1三种方式对比
Thread 类方式:
优点:提供操作线程的API的多;能根据自己需要创建对应的线程;
缺点:频繁的创建和消耗比较好资源;提供操作线程的API不是马上响应(线程是操作系统统一管理,收到指令之后,具体还得操作系统真实处理,而操作系统收到指令之后并非马上执行相关指令);
TreadPool 池化方式:
优点:池化线程进行管理,需要使用就从池中获取就行,避免频繁创建和销毁线程;从而可以达到线程的复用;
缺点:提供的API太少,线程等待顺序控制比较弱;从而在一些业务情况下操作不方便;
Task:
优点:在ThreadPool的思想进行了封装,继承了ThreadPool的优点;提供了丰富的线程控制API,从而方便了开发;
Task多种方式创建线程
1.2 Task.Wait
.Wait() 等待执行调用任务完成,然后执行下一步; 及阻塞了主线程;
Console.WriteLine($"主线程{Thread.CurrentThread.ManagedThreadId}开启");
var task = Task.Run(() =>
{
Console.WriteLine($"Task 开启线程{Thread.CurrentThread.ManagedThreadId}处理业务");
Thread.Sleep(2000);
});
task.Wait();
Console.WriteLine($"主线程{Thread.CurrentThread.ManagedThreadId}完成");
运行结果
主线程1开启
Task 开启线程3处理业务
主线程1完成
1.3 Task.ContinueWith
ContinueWith() 等调用者结束之后才进行调用里面的相关业务,由线程池分配线程进行处理接下来的业务,不阻塞主线程,但却能控制业务之间的先后顺序;
Console.WriteLine($"主线程{Thread.CurrentThread.ManagedThreadId}开启");
var task = Task.Run(() =>
{
Console.WriteLine($"Task 开启线程{Thread.CurrentThread.ManagedThreadId}处理业务");
Thread.Sleep(2000);
});
task.ContinueWith(t=>
{
Console.WriteLine($"TaskContinueWith 开启线程{Thread.CurrentThread.ManagedThreadId}处理业务");
});
Console.WriteLine($"主线程{Thread.CurrentThread.ManagedThreadId}完成");
Console.ReadKey();
运行结果
主线程1开启
主线程1完成
Task 开启线程3处理业务
TaskContinueWith 开启线程4处理业务
1.4 Task.WaitAll
Task.WaitAll等到所有任务都完成之后,才进行主线程的下一步操作,即阻塞主线程;
1.5 Task.WaitAny
Task.WaitAny等到其中一个任务都完成之后,才进行主线程的下一步操作,其中任务没有完成之前也阻塞主线程;
1.6 Task.Factory.ContinueWhenAll
当ContinueWhenAll中所有任务都完成时执行回调方法,不阻塞主线程:
Console.WriteLine($"主线程{Thread.CurrentThread.ManagedThreadId}开启");
var task1 = Task.Run(() =>
{
Console.WriteLine($"Task1 开启线程begin{Thread.CurrentThread.ManagedThreadId}处理业务");
Thread.Sleep(2000);
Console.WriteLine($"Task1 开启线程end{Thread.CurrentThread.ManagedThreadId}处理业务");
});
var task2 = Task.Run(() =>
{
Console.WriteLine($"Task2 开启线程begin{Thread.CurrentThread.ManagedThreadId}处理业务");
Thread.Sleep(3000);
Console.WriteLine($"Task2 开启线程end{Thread.CurrentThread.ManagedThreadId}处理业务");
});
var task3 = Task.Run(() =>
{
Console.WriteLine($"Task3 开启线程begin{Thread.CurrentThread.ManagedThreadId}处理业务");
Thread.Sleep(5000);
Console.WriteLine($"Task3 开启线程end{Thread.CurrentThread.ManagedThreadId}处理业务");
});
List<Task> listTask = new List<Task> { task1, task2, task3 };
Task.Factory.ContinueWhenAll(listTask.ToArray(), tasks => {
Console.WriteLine($"任务执行结束");
});
Console.WriteLine($"主线程{Thread.CurrentThread.ManagedThreadId}完成");
Console.ReadKey();
运行结果
主线程1开启
Task1 开启线程begin4处理业务
Task2 开启线程begin5处理业务
Task3 开启线程begin3处理业务
主线程1完成
Task1 开启线程end4处理业务
Task2 开启线程end5处理业务
Task3 开启线程end3处理业务
任务执行结束
如上图,不阻塞主线程, 当封装在所有list中的任务全部执行完成之后,再进行后续的处理,其中后续处理的业务的参数是上一完成任务的列表!!!
注: 子线程开启时,线程之间的顺序是不可控制的,由操作系统根据资源情况进行分配处理;
1.7 Task.Factory.ContinueWhenAny
当参数中的任务有一个完成之后就进行回调,执行下一个任务。
主线程1开启
主线程1完成
Task1 开启线程begin4处理业务
Task3 开启线程begin5处理业务
Task2 开启线程begin3处理业务
Task1 开启线程end4处理业务
其中一个任务执行结束
Task2 开启线程end3处理业务
Task3 开启线程end5处理业务
1.8 CancellationTokenSource 取消任务
在任务执行的过程中,常常会有需求进行任务的取消,有的会借用公共第三方变量进行任务控制是否继续执行,如:true执行,false不执行;在Task中,提供了CancellationTokenSource进行任务取消。如下:
如上代码说明,在task1执行完成之后,主动调用Cancel方法取消任务,task2就检测到任务取消,即退出;
自动取消:
如上图,通过指定一个时间,然后时间到了自动取消任务,也可以使用cancelTokenSource.CancelAfter(3000); 这样也能实现自动取消;
Console.WriteLine($"主线程{Thread.CurrentThread.ManagedThreadId}开启");
var cts = new CancellationTokenSource();
var task1 = Task.Run(() =>
{
Console.WriteLine($"Task1 开启线程begin{Thread.CurrentThread.ManagedThreadId}处理业务");
Thread.Sleep(2000);
Console.WriteLine($"Task1 开启线程end{Thread.CurrentThread.ManagedThreadId}处理业务");
cts.Cancel();//取销执行
},cts.Token);
var task2 = Task.Run(() =>
{
Console.WriteLine($"Task2 开启线程begin{Thread.CurrentThread.ManagedThreadId}处理业务");
// Thread.Sleep(3000);
for (int i=1;i<=10;i++)
{
if (cts.IsCancellationRequested)
{
return;
}
Thread.Sleep(1000);
}
Console.WriteLine($"Task2 开启线程end{Thread.CurrentThread.ManagedThreadId}处理业务");
},cts.Token);
var task3 = Task.Run(() =>
{
Console.WriteLine($"Task3 开启线程begin{Thread.CurrentThread.ManagedThreadId}处理业务");
Thread.Sleep(5000);
Console.WriteLine($"Task3 开启线程end{Thread.CurrentThread.ManagedThreadId}处理业务");
});
List<Task> listTask = new List<Task> { task1, task2, task3 };
Task.WaitAll(listTask.ToArray());
Console.WriteLine($"主线程{Thread.CurrentThread.ManagedThreadId}完成");
Console.ReadKey();
运行结果
主线程1开启
Task1 开启线程begin4处理业务
Task3 开启线程begin5处理业务
Task2 开启线程begin3处理业务
Task1 开启线程end4处理业务
Task3 开启线程end5处理业务
主线程1完成
参考:www.cnblogs.com/zoe-zyq/p/1…
二 Ioc
.NetCore项目中Ioc及集成Autofac的使用看着比较简单
参考:www.cnblogs.com/zoe-zyq/p/1…
三 泛型缓存
public class Person
{
/// <summary>
/// 姓名
/// </summary>
public string Name { get; set; }
}
public class Student : Person
{
}
public class Worker : Person
{
}
不使用缓存
public class SayHelloBuiler<T> where T:Person
{
public static string BuilerSayHello<T>(T t)
{
Type type = typeof(T);
string sayhelloStri = $"我是:{type.Name},我叫:{type.GetProperty("Name").GetValue(t)}";
Console.WriteLine("每次新建");
return sayhelloStri;
}
}
调用
public static void Demo1()
{
Student st = new Student() { Name = "张三" };
string strdemo1 = SayHelloBuiler<Student>.BuilerSayHello(st);
Console.WriteLine(strdemo1);
string strdemo2 = SayHelloBuiler<Student>.BuilerSayHello(st);
Console.WriteLine(strdemo2);
string strdemo3 = SayHelloBuiler<Student>.BuilerSayHello(st);
Console.WriteLine(strdemo3);
Worker wk = new Worker() { Name = "万商" };
string strdemo4 = SayHelloBuiler<Worker>.BuilerSayHello(wk);
Console.WriteLine(strdemo4);
}
结果
每次新建
我是:Student,我叫:张三
每次新建
我是:Student,我叫:张三
每次新建
我是:Student,我叫:张三
每次新建
我是:Worker,我叫:万商
使用缓存
public class SayHelloBuilerEx<T> where T:Person
{
static string sayhellostr = string.Empty;
static SayHelloBuilerEx()
{
Type type = typeof(T);
sayhellostr = $"我是:{type.Name},我叫:{{0}}";
Console.WriteLine("每次新建");
}
public static string GetSayHelloString()
{
return sayhellostr;
}
}
public static void Demo2()
{
Student st = new Student() { Name = "张三" };
string strdemo1 = SayHelloBuilerEx<Student>.GetSayHelloString();
Console.WriteLine(string.Format(strdemo1, st.Name));
string strdemo2 = SayHelloBuilerEx<Student>.GetSayHelloString();
Console.WriteLine(string.Format(strdemo2, st.Name));
string strdemo3 = SayHelloBuilerEx<Student>.GetSayHelloString();
Console.WriteLine(string.Format(strdemo3, st.Name));
Worker wk = new Worker() { Name = "万商" };
string strdemo4 = SayHelloBuilerEx<Worker>.GetSayHelloString();
Console.WriteLine(string.Format(strdemo4, wk.Name));
}
运行结果
每次新建
我是:Student,我叫:张三
我是:Student,我叫:张三
我是:Student,我叫:张三
每次新建
我是:Worker,我叫:万商
不使用缓存,那个字符串每次都要创建,而是使用缓存就只创建一次。
参考:www.cnblogs.com/zoe-zyq/p/1…
四 过滤器的使用
这篇文章主要讲了netcore自动校验,关闭方法,和全局过滤器配置使用。
参考:www.cnblogs.com/zoe-zyq/p/1…
五 aop 切面
这个应用到服务层,其他应用层使用中间件或者过滤器。
这篇文章讲得非常好,切面编程入门学习。
参考:www.cnblogs.com/zoe-zyq/p/1…
六 日志配置
参考:www.cnblogs.com/zoe-zyq/p/1…
七 jwt 用法
参考:www.cnblogs.com/zoe-zyq/p/1…
八 相关流程和配置
参考:www.cnblogs.com/zoe-zyq/p/1…
九 依赖注入ioc
通过容器创建的出来的对象,根据不同的注入方式有以下三种生命周期:
-
Singleton(单例) :整个根容器的生命周期内是同一个对象;通过 services.AddSingleton()方法进行注册;
-
Scoped(作用域) :在容器或子容器的生命周期内,对象保持一致,如果容器释放掉,那就意味着对象也会释放掉;通过 services.AddScoped()方法进行注册;
-
Transient(瞬时) : 每次使用都会创建新的实例;通过 services.AddTransient()方法进行注册;
注:services 是 IServiceCollection services ;
Asp.NetCore自带依赖注入的注册方式,如下:
在Asp.NetCore中,框架默认将其分有根作用域(与引用程序同生命周期)和请求作用域(每一个请求一个作用域),通常也会称其为根容器和请求容器,名称分别为ApplicationServices和RequestServices,在程序中获取方式如下:
- 通过IApplicationBuilder对象可以获取ApplicationServices
- 通过HttpContext.RequestServices获取RequestServices
这个例子不错
[HttpGet]
public string Get([FromServices]ITestSingleton testSingleton, [FromServices]ITestSingleton testSingleton1,
[FromServices]ITestScoped testScoped, [FromServices]ITestScoped testScoped1,
[FromServices]ITestTransient testTransient, [FromServices]ITestTransient testTransient1)
{
//获取请求作用域(请求容器)
var requestServices = HttpContext.RequestServices;
//在请求作用域下创建子作用域
using(IServiceScope scope = requestServices.CreateScope())
{
//在子作用域中通过其容器获取注入的不同生命周期对象
ITestSingleton testSingleton11 = scope.ServiceProvider.GetService<ITestSingleton>();
ITestScoped testScoped11 = scope.ServiceProvider.GetService<ITestScoped>();
ITestTransient testTransient11 = scope.ServiceProvider.GetService<ITestTransient>();
ITestSingleton testSingleton12 = scope.ServiceProvider.GetService<ITestSingleton>();
ITestScoped testScoped12 = scope.ServiceProvider.GetService<ITestScoped>();
ITestTransient testTransient12 = scope.ServiceProvider.GetService<ITestTransient>();
Console.WriteLine("================Singleton=============");
Console.WriteLine($"请求作用域的ITestSingleton对象:{testSingleton.GetHashCode()}");
Console.WriteLine($"请求作用域的ITestSingleton1对象:{testSingleton1.GetHashCode()}");
Console.WriteLine($"请求作用域下子作用域的ITestSingleton11对象:{testSingleton11.GetHashCode()}");
Console.WriteLine($"请求作用域下子作用域的ITestSingleton12对象:{testSingleton12.GetHashCode()}");
Console.WriteLine("================Scoped=============");
Console.WriteLine($"请求作用域的ITestScoped对象:{testScoped.GetHashCode()}");
Console.WriteLine($"请求作用域的ITestScoped1对象:{testScoped1.GetHashCode()}");
Console.WriteLine($"请求作用域下子作用域的ITestScoped11对象:{testScoped11.GetHashCode()}");
Console.WriteLine($"请求作用域下子作用域的ITestScoped12对象:{testScoped12.GetHashCode()}");
Console.WriteLine("================Transient=============");
Console.WriteLine($"请求作用域的ITestTransient对象:{testTransient.GetHashCode()}");
Console.WriteLine($"请求作用域的ITestTransient1对象:{testTransient1.GetHashCode()}");
Console.WriteLine($"请求作用域下子作用域的ITestTransient11对象:{testTransient11.GetHashCode()}");
Console.WriteLine($"请求作用域下子作用域的ITestTransient12对象:{testTransient12.GetHashCode()}");
}
return "TestServiceScope";
}
运行,进行请求,看打印结果:
- 对于Singleton来说始终不变,因为其是跟随根容器生命周期,引用程序退出才释放;
- 对于Scoped来说只要在自己的作用域内就是单例的;
- 对于Transient来说始终创建;
总结:单例在整个生命周期只有一个,作用域是在子容器中只有一个(同一个请求里面只有一个,不同请求不相同),瞬时每次都不一样。
参考:www.cnblogs.com/zoe-zyq/p/1…
为啥使用第三方依赖注入的工具,因为自身的依赖注入不支持
- 属性注入
- 名称注入
- 注册方式,自身必须指定具体的类型
- 扩展功能
上面这个有属性注入和构造注入实例,可以参考。
代码简单说明及注意:
-
代码中的AsImplementedInterfaces是暴露所有接口;
-
Assembly.Load加载程序集,有多种加载方式如Assembly.LoadFrom/LoadFile等,需要注意每个方法的使用前提,不然找不到程序集文件;
-
注册时其中的Where是用来过滤类型,找到符合条件的类型;
运行调试,都注入上了,以后在对应程序集中添加类型就不用每次在注册的地方添加该类型的注册代码了
控制器属性注入
刚才遗留的控制器属性注入问题,为什么放在这里说呢,因为要用到程序注册方式,两步走:
这样就可以在Controller中进行属性注入了;运行,之前不能注入的都成功了;当然还有其他的方式,这里没有深入;
这里还有aop 切面相关内容
通常,在项目中,业务层做逻辑处理、数据处理比较多,通常我们会在业务层进行相关缓存处理及其他处理,所以这里在业务层中举例演示(当然其他层也可以):
既然要进行切面处理,得有个地方处理,所以得增加个拦截器,Nuget先安装一个包:Autofac.Extras.DynamicProxy;
注册的时候开启切面功能,并指定对应拦截器进行处理;
这样就能通过拦截器扩展相关功能,业务逻辑前后都行,也可以不调用原有的业务代码,通过拦截器短路,运行结果如下:
可以看到,请求完之后,在对应的业务处理前后都进行了对应处理,这里之所以有两次,那是因为上面测试代码调用了两次;
像这种无嵌入型进行扩展的AOP编程方式,是非常有用的,扩展和维护功能也比较方便,有人说性能问题有降低,当然会有那么一点,相对来说,是可以接受的,除非是扩展业务逻辑耗时多,这就需要另想方案了;