C# 异步编程详解(Task,async/await)
文章目录
- 1.什么是异步
- 2.Task 产生背景
- 3.Thread(线程) 和 Task(异步)的区别
- 3.1 几个名词
- 3.2 Thread 与 Task 的区别
- 4.Task API
- 4.1 创建和启动任务
- 4.2 Task 等待、延续和组合
- 4.3 task.Result
- 4.4
Task.Delay()
和Thread.Sleep()
区别
- 5.CancellationToken 和 CancellationTokenSource 取消线程
- 5.1 CancellationToken
- 5.2 CancellationTokenSource
- 5.3 示例
- 6.
async
与await
- 7.微软案例
1.什么是异步
同步和异步主要用于修饰方法。当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,我们称这个方法是同步方法;当一个方法被调用时立即返回,并获取一个线程执行该方法内部的业务,调用者不用等待该方法执行完毕,我们称这个方法为异步方法。
异步的好处在于非阻塞(调用线程不会暂停执行去等待子线程完成),因此我们把一些不需要立即使用结果、较耗时的任务设为异步执行,可以提高程序的运行效率。net4.0在ThreadPool的基础上推出了Task类,微软极力推荐使用Task来执行异步任务,现在C#类库中的异步方法基本都用到了Task;net5.0推出了async/await,让异步编程更为方便。
2.Task 产生背景
Task
出现之前,微软的多线程处理方式有:Thread→ThreadPool→委托的异步调用
,虽然也可以基本业务需要的多线程场景,但它们在多个线程的等待处理方面、资源占用方面、线程延续和阻塞方面、线程的取消方面等都显得比较笨拙,在面对复杂的业务场景下,显得有点捉襟见肘了。
ThreadPool
相比Thread
来说具备了很多优势,但是ThreadPool
却又存在一些使用上的不方便。比如:
ThreadPool
不支持线程的取消、完成、失败通知等交互性操作;ThreadPool
不支持线程执行的先后次序;
正是在这种背景下,Task应运而生。Task是微软在.Net 4.0时代推出来的,也是微软极力推荐的一种多线程的处理方式,Task看起来像一个Thread,实际上,它是在ThreadPool的基础上进行的封装,Task的控制和扩展性很强,在线程的延续、阻塞、取消、超时等方面远胜于Thread和ThreadPool。以下是一个简单的任务示例:
static void Main(string[] args)
{Task t = new Task(() =>{Console.WriteLine("任务开始工作……");Thread.Sleep(5000); //模拟工作过程});t.Start();t.ContinueWith(task =>{Console.WriteLine("任务完成,完成时候的状态为:");Console.WriteLine("IsCanceled={0}\tIsCompleted={1}\tIsFaulted={2}", task.IsCanceled, task.IsCompleted, task.IsFaulted);});Console.ReadKey();
}
3.Thread(线程) 和 Task(异步)的区别
3.1 几个名词
- 1、进程(process): 当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。而一个进程又是由多个线程所组成的。
- 2、线程(thread): 线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的。多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
- 前台线程: 前台线程是不会被立即关闭的,它的关闭只会发生在自己执行完成时,不受外在因素的影响。假如应用程序退出,造成它的前台线程终止,此时CLR仍然保持活动并运行,使应用程序能继续运行,当它的的前台线程都终止后,整个进程才会被销毁。
(Thread类默认创建的是前台线程)
- 后台线程: 后台线程是可以随时被CLR关闭而不引发异常的,也就是说当后台线程被关闭时,资源的回收是立即的,不等待的,也不考虑后台线程是否执行完成,就算是正在执行中也立即被终止。
(通过线程池/Task创建的线程都是后台线程)
- 前台线程: 前台线程是不会被立即关闭的,它的关闭只会发生在自己执行完成时,不受外在因素的影响。假如应用程序退出,造成它的前台线程终止,此时CLR仍然保持活动并运行,使应用程序能继续运行,当它的的前台线程都终止后,整个进程才会被销毁。
- 3、同步(sync): 发出一个功能调用时,在没有得到结果之前,该调用就不返回。
- 4、异步(async): 与同步相对,调用在发出之后,这个调用就直接返回了,所以没有返回结果。当这个调用完成后,一般通过状态、通知和回调来通知调用者。对于异步调用,调用的返回并不受调用者控制。
通知调用者的三种方式:- 状态:即监听被调用者的状态(轮询),调用者需要每隔一定时间检查一次,效率会很低。
- 通知:当被调用者执行完成后,发出通知告知调用者,无需消耗太多性能。
- 回调:与通知类似,当被调用者执行完成后,会调用调用者提供的回调函数。
- 5、阻塞(block): 阻塞调用是指调用结果返回(或者收到通知)之前,当前线程会被挂起,即不继续执行后续操作。简单来说,等前一件做完了才能做下一件事。
- 6、非阻塞(non-block): 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
3.2 Thread 与 Task 的区别
Thread
类主要用于实现线程的创建以及执行。
Task
类表示以异步方式执行的单个操作。
1、Task
是基于 Thread
的,是比较高层级的封装,Task
最终还是需要 Thread
来执行
2、Task
默认使用后台线程执行,Thread
默认使用前台线程
static void Main(string[] args)
{Thread thread = new Thread(obj => { Thread.Sleep(3000); });thread.Start();
}// 上面代码,tread为前台线程,主程序在3秒后结束。
static void Main(string[] args)
{Task<int> task = new Task<int>(() => {Thread,Sleep(3000);return 1;});task.Start();
}// 上面代码,task为后台线程,主程序会瞬间结束。
3、Task
可以有返回值,Thread
没有返回值
public static void Main(string[] args)
{Task<int> task = new Task<int>(LongRunningTask);task.Start();Console.WriteLine(task.Result);
} private static int LongRunningTask()
{Thread.Sleep(3000);return 1;
}
4、Task
可以执行后续操作,Thread
不能执行后续操作
4.Task API
4.1 创建和启动任务
不带返回值:
//1. new方式实例化一个Task,需要通过Start方法启动
Task task1 = new Task(() =>
{Thread.Sleep(100);Console.WriteLine($"hello, task1的线程ID为{Thread.CurrentThread.ManagedThreadId}");
});
task1.Start();//2. Task.Factory.StartNew(Action action)创建和启动一个Task
Task task2 = Task.Factory.StartNew(() =>
{Thread.Sleep(100);Console.WriteLine($"hello, task2的线程ID为{ Thread.CurrentThread.ManagedThreadId}");
});//3. Task.Run(Action action)将任务放在线程池队列,返回并启动一个Task
Task task3 = Task.Run(() =>
{Thread.Sleep(100);Console.WriteLine($"hello, task3的线程ID为{ Thread.CurrentThread.ManagedThreadId}");
});Console.WriteLine("执行主线程!");
Console.ReadKey();
执行主线程!
hello, task1的线程ID为4
hello, task2的线程ID为6
hello, task3的线程ID为7
带返回值:
// 1.new方式实例化一个Task,需要通过Start方法启动
Task<string> task1 = new Task<string>(() =>
{return $"hello, task1的ID为{Thread.CurrentThread.ManagedThreadId}";
});
task1.Start();// 2.Task.Factory.StartNew(Func func)创建和启动一个Task
Task<string> task2 =Task.Factory.StartNew<string>(() =>
{return $"hello, task2的ID为{ Thread.CurrentThread.ManagedThreadId}";
});// 3.Task.Run(Func func)将任务放在线程池队列,返回并启动一个Task
Task<string> task3= Task.Run<string>(() =>
{return $"hello, task3的ID为{ Thread.CurrentThread.ManagedThreadId}";
});Console.WriteLine("执行主线程!");
Console.WriteLine(task1.Result);// 注意task.Result获取结果时会阻塞UI主线程
Console.WriteLine(task2.Result);
Console.WriteLine(task3.Result);
Console.ReadKey();
执行主线程!
hello, task1的ID为4
hello, task2的ID为6
hello, task3的ID为7
4.2 Task 等待、延续和组合
- Wait: 针对单个Task的实例,可以task1.wait进行线程等待(阻塞主线程)
- WaitAny: 线程列表中任何一个线程执行完毕即可执行(阻塞主线程)
- WaitAll: 线程列表中所有线程执行完毕方可执行(阻塞主线程)
- WhenAny: 与ContinueWith配合,线程列表中任何一个执行完毕,则继续ContinueWith中的任务(开启新线程,不阻塞主线程)
- WhenAll: 与ContinueWith配合,线程列表中所有线程执行完毕,则继续ContinueWith中的任务(开启新线程,不阻塞主线程)
- ContinueWith: 与WhenAny或WhenAll配合使用
- ContinueWhenAny: 等价于Task的WhenAny+ContinueWith
- ContinueWhenAll: 等价于Task的WhenAll+ContinueWith
//创建一个任务
Task<int> task = Task.Run<int>(() =>
{int sum = 0;Console.WriteLine("使用`Task`执行异步操作.");for (int i = 0; i < 1000; i++){sum += i;}return sum;
});Console.WriteLine("主线程执行其他处理");
//任务完成时执行处理。
Task cwt = task.ContinueWith(t =>
{Console.WriteLine("任务完成后的执行结果:{0}", t.Result.ToString());
});task.Wait();
cwt.Wait();Action<string,int> log = (name,time) =>
{Console.WriteLine($"{name}任务开始...");Thread.Sleep(time);Console.WriteLine($"{name}任务结束!");
};
List<Task> tasks = new List<Task>
{Task.Run(() => log("张三",3000)),Task.Run(() => log("李四",1000)),Task.Run(() => log("王五",2000))
};
//以下语句逐个测试效果
Task.WaitAny(tasks.ToArray());
Task.WaitAll(tasks.ToArray());
Task.WhenAny(tasks.ToArray()).ContinueWith(x => Console.WriteLine("某个Task执行完毕"));
Task.WhenAll(tasks.ToArray()).ContinueWith(x => Console.WriteLine("所有Task执行完毕"));
Task.Factory.ContinueWhenAny(tasks.ToArray(), x => Console.WriteLine("某个Task执行完毕"));
Task.Factory.ContinueWhenAll(tasks.ToArray(), x => Console.WriteLine("所有Task执行完毕"));Console.ReadKey();
4.3 task.Result
等待获取task返回值,阻塞调用其他线程,直到当前异步操作完成,相当于调用wait方法
static void Main(string[] args)
{Task<string> task = Task.Run<string>(() => {Thread.Sleep(3000);return "ming_堵塞线程";});Console.WriteLine(task.Result);Console.WriteLine("主线程执行");Console.ReadKey();
}
ming_堵塞线程
主线程执行
4.4 Task.Delay()
和 Thread.Sleep()
区别
Thread.Sleep()
是同步延迟,Task.Delay()
是异步延迟。Thread.Sleep()
会阻塞线程,Task.Delay()
不会。Thread.Sleep()
不能取消,Task.Delay()
可以。Task.Delay()
和Thread.Sleep()
最大的区别是Task.Delay()
旨在异步运行,在同步代码中使用Task.Delay()
是没有意义的;在异步代码中使用Thread.Sleep()
是一个非常糟糕的主意。通常使用await
关键字调用Task.Delay()
。
// 阻塞,出现CPU等待...
static void Main(string[] args)
{// 阻塞,出现CPU等待...Task.Factory.StartNew(() =>{Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " ****** Start Sleep()******");for (int i = 1; i <=10; i++){Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + "******Sleep******==>" + i);Thread.Sleep(1000);//同步延迟,阻塞一秒}Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " ******End Sleep()******");Console.WriteLine();});// 不阻塞Task.Factory.StartNew(() =>{Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " ======StartDelay()======");for (int i =1; i <=10; i++){Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " ======Delay====== ==>" + i);Task.Delay(1000);//异步延迟}Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " ======End Delay()======");Console.WriteLine();});// 不阻塞等待三秒Task.Factory.StartNew(async() =>{Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " ======StartDelay()======");for (int i =1; i <=10; i++){Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " ======Await Delay====== ==>" + i);await Task.Delay(1000);//异步延迟}Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " ======End Delay()======");Console.WriteLine();});Console.ReadKey();
}
5.CancellationToken 和 CancellationTokenSource 取消线程
5.1 CancellationToken
属性:
//表示当前CancellationToken是否可以被取消
public bool CanBeCanceled { get; }
//表示当前CancellationToken是否已经是取消状态
public bool IsCancellationRequested { get; }
方法:
//往CancellationToken中注册回调
public CancellationTokenRegistration Register(Action callback);
//当CancellationToken处于取消状态时,抛出System.OperationCanceledException异常
public void ThrowIfCancellationRequested();
5.2 CancellationTokenSource
属性:
//表示Token是否已处于取消状态
public bool IsCancellationRequested { get; }
//CancellationToken 对象
public CancellationToken Token { get; }
方法:
//立刻取消
public void Cancel();
//立刻取消
public void Cancel(bool throwOnFirstException);
//延迟指定时间后取消
public void CancelAfter(int millisecondsDelay);
//延迟指定时间后取消
public void CancelAfter(TimeSpan delay);
5.3 示例
CancellationTokenSource source = new CancellationTokenSource();
//注册一个线程取消后执行的逻辑
source.Token.Register(() =>
{//这里执行线程被取消后的业务逻辑.Console.WriteLine("-------------我是线程被取消后的业务逻辑---------------------");
});Task.Run(() =>
{while (!source.IsCancellationRequested){Thread.Sleep(100);Console.WriteLine("当前thread={0} 正在运行", Thread.CurrentThread.ManagedThreadId);}
}, source.Token);Thread.Sleep(2000);
source.Cancel();
当前thread=4 正在运行
当前thread=4 正在运行
当前thread=4 正在运行
当前thread=4 正在运行
当前thread=4 正在运行
当前thread=4 正在运行
当前thread=4 正在运行
当前thread=4 正在运行
当前thread=4 正在运行
当前thread=4 正在运行
当前thread=4 正在运行
当前thread=4 正在运行
当前thread=4 正在运行
当前thread=4 正在运行
当前thread=4 正在运行
当前thread=4 正在运行
当前thread=4 正在运行
当前thread=4 正在运行
-------------我是线程被取消后的业务逻辑---------------------
当前thread=4 正在运行
6.async
与 await
async:
async
修饰符可将方法、lambda 表达式或匿名方法
指定为异步。异步方法名字后习惯加个Async后缀
async
关键字修饰的方法一般包含一个或多个await
表达式或语句,如果不包含 await 表达式或语句,则该方法将同步执行
。 编译器警告将通知你不包含 await 语句的任何异步方法。async
方法可以是下面三种返回类型:- Task
- Task< TResult >
- void 这种返回类型一般用在event事件处理器中,或者用在你只需要任务执行,不关心任务执行结果的情况当中。
- 任何其他具有GetAwaiter方法的类型(从C#7.0开始)
await:
await
关键字只能在async
关键字修饰的方法(异步方法)中使用。- await 运算符的操作数通常是以下其中一个 .NET 类型:Task、Task、ValueTask 或 ValueTask。 但是,任何可等待表达式都可以是 await 运算符的操作数。
示例:
无返回值:
static void Main(string[] args)
{Console.WriteLine("主线程--开始");var task = TestTaskAsync();task.ContinueWith(t => Console.WriteLine("TestTaskAsync方法结束后执行"));Console.WriteLine("主线程--结束");Console.ReadKey();
}private static async Task TestTaskAsync()
{Console.WriteLine("开始执行TestTaskAsync方法");Task task = new Task(() =>{Console.WriteLine("开始子线程耗时操作");Thread.Sleep(4000);Console.WriteLine("结束子线程耗时操作");});task.Start();await task;Console.WriteLine("await关键字后面的内容 1");
}
带返回值:
// 方法一:使用ContinueWith
Task<int> task = TestTaskIntAsync();
task.ContinueWith((t) =>
{COnsole.WriteLine($"TestTaskIntAsync的返回值是:{t.Result.ToString()}");
});
// 方法二:使用await
Task<int> task = TestTaskIntAsync();
int result = await task;
Console.WriteLine($"TestTaskIntAsync的返回值是:{result }");
7.微软案例
以微软文档的做早餐的案例加以简化来讲解
1.同步执行
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;namespace ThreadTest
{class Program{static void Main(string[] args){Stopwatch stopwatch = new Stopwatch();stopwatch.Start();PourOJ();PourCoffee();ToastBread();FryBacon();FryEggs();Console.WriteLine("早餐已经做完!");stopwatch.Stop();Console.WriteLine($"做早餐总计耗时:{stopwatch.ElapsedMilliseconds}");Console.ReadLine();}//倒橙汁private static void PourOJ(){Thread.Sleep(1000);Console.WriteLine("倒一杯橙汁");}//烤面包private static void ToastBread(){Console.WriteLine("开始烤面包");Thread.Sleep(3000);Console.WriteLine("烤面包好了");}//煎培根private static void FryBacon(){Console.WriteLine("开始煎培根");Thread.Sleep(6000);Console.WriteLine("培根煎好了");}//煎鸡蛋private static void FryEggs(){Console.WriteLine("开始煎鸡蛋");Thread.Sleep(6000);Console.WriteLine("鸡蛋好了");}//倒咖啡private static void PourCoffee(){Thread.Sleep(1000);Console.WriteLine("倒咖啡");}}
}
2.并行执行
如果此时我们每一项任务都有一个单独的人去完成
那么可以如下:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;namespace ThreadTest
{class Program{static void Main(string[] args){Test();Console.ReadLine();}private static void Test(){Stopwatch stopwatch = new Stopwatch();stopwatch.Start();List<Task> tasks = new List<Task>() { PourOJ(), ToastBread(), FryBacon(), FryEggs(), PourCoffee() };Task.WhenAll(tasks).ContinueWith((t)=> {Console.WriteLine("早餐已经做完!");stopwatch.Stop();Console.WriteLine($"做早餐总计耗时:{stopwatch.ElapsedMilliseconds}");});}//倒橙汁private static async Task PourOJ(){await Task.Delay(1000);Console.WriteLine("倒一杯橙汁");}//烤面包private static async Task ToastBread(){Console.WriteLine("开始烤面包");await Task.Delay(3000);Console.WriteLine("烤面包好了");}//煎培根private static async Task FryBacon(){Console.WriteLine("开始煎培根");await Task.Delay(6000);Console.WriteLine("培根煎好了");}//煎鸡蛋private static async Task FryEggs(){Console.WriteLine("开始煎鸡蛋");await Task.Delay(6000);Console.WriteLine("鸡蛋好了");}//倒咖啡private static async Task PourCoffee(){await Task.Delay(1000);Console.WriteLine("倒咖啡");}}
}
3.并行且可指定顺序执行
现在呢,有个问题,不可能每次做早餐你都有那么多帮手,同时帮你,如果现在要求,先倒橙汁,然后倒咖啡,其余的操作并行执行,应该如何操作呢?
只需将以上案例的Test 方法修改如下:
private static async void Test()
{Stopwatch stopwatch = new Stopwatch();stopwatch.Start();await PourOJ();await PourCoffee(); List<Task> tasks = new List<Task>() { ToastBread(), FryBacon(), FryEggs() };await Task.WhenAll(tasks);Console.WriteLine("早餐已经做完!");stopwatch.Stop();Console.WriteLine($"做早餐总计耗时:{stopwatch.ElapsedMilliseconds}");
}
相关文章:

C# 异步编程详解(Task,async/await)
文章目录 1.什么是异步2.Task 产生背景3.Thread(线程) 和 Task(异步)的区别3.1 几个名词3.2 Thread 与 Task 的区别 4.Task API4.1 创建和启动任务4.2 Task 等待、延续和组合4.3 task.Result4.4 Task.Delay() 和 Thread.Sleep() 区别 5.CancellationToken 和 CancellationToken…...

qt结合vs2022安装
进入清华大学开源软件: 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 下载完成后,双击进行安装: 进入邮箱进行验证: 可能是因为网络问题,无法安装。 重新安装5.12.12版本。 安装后启动失败,重新…...

Kafka集群部署(手把手部署图文详细版)
1.1.1 部署zookpeer 在node02下载并解压zookeeper软件包 cd /usr/local wget https://archive.apache.org/dist/zookeeper/zookeeper-3.4.6/zookeeper-3.4.6.tar.gz 或者:scp cat192.168.28.100:/home/cat/zookeeper-3.4.6.tar.gz /tmp(注意目录…...

阿里Qwen2-72B大模型已是开源榜的王者,为什么还要推出其他参数模型,被其他模型打榜?
6 月 27 日,全球知名的开源平台 Hugging Face 的联合创始人兼首席执行官 Clem 在社交平台激动宣布,阿里 Qwen2-72B 成为了开源模型排行榜的王者。 这是一件大好事,说明了我们在大模型领域从先前的追赶,逐渐走向了领导,…...

7.基于SpringBoot的SSMP整合案例-表现层开发
目录 1.基于Restfu1进行表现层接口开发 1.1创建功能类 1.2基于Restful制作表现层接口 2.接收参数 2使用Apifox测试表现层接口功能 保存接口: 分页接口: 3.表现层一致性处理 3.1先创建一个工具类,用作后端返回格式统一类:…...

【server】3、注册中心与配置中心
1、服务注册与发现 1.1、consul 1.1.1 是什么 官网: Consul by HashiCorp spring-cloud-consul: Spring Cloud Consul :: Spring Cloud Consul gitHub 官网 :GitHub - hashicorp/consul: Consul is a distributed, highly available, and data cent…...

【大数据】—量化交易实战案例(海龟交易策略)
声明:股市有风险,投资需谨慎!本人没有系统学过金融知识,对股票有敬畏之心没有踏入其大门,今天用另外一种方法模拟炒股,后面的模拟的实战全部用同样的数据,最后比较哪种方法赚的钱多。 海龟交易…...

014-GeoGebra基础篇-快速解决滑动条的角度无法输入问题
有客户反馈,他的Geogebra一直有个bug,那就是输入角度最大值时总不按照他设定的展示,快被气炸了~ 目录 一、问题复现(1)插入一个滑动条(2)选择Angle(3)输入90,…...

Diffusion模型的微调和引导
留意后续更新,欢迎关注微信公众号:组学之心 Diffusion模型的微调和引导 微调(fine-tuning): 从一个已经训练过的模型开始训练,我们就可以从一个学会如何“去噪”的模型开始训练,相对于随机初始…...

零基础学MySQL:从入门到实践的完整指南
引言: MySQL,作为全球最受欢迎的开源关系型数据库管理系统之一,以其高性能、易用性和灵活性,在Web开发、数据分析等领域占据着举足轻重的地位。如果你是一位编程新手,想要踏入数据库管理的大门,本文将从零…...

澳蓝荣耀时刻,6款产品入选2024年第一批《福州市名优产品目录》
近日,福州市工业和信息化局公布2024年第一批《福州市名优产品目录》,澳蓝自主研发生产的直接蒸发冷却空调、直接蒸发冷却组合式空调机组、间接蒸发冷水机组、高效间接蒸发冷却空调机、热泵式热回收型溶液调湿新风机组、防火湿帘6款产品成功入选。 以上新…...

Frrouting快速入门——OSPF组网(一)
FRR简介 FRR是FRRouting的简称,是一个开源的路由交换软件套件。其作者源自老牌项目quaga的成员,也可以算是quaga的新版本。 使用时一般查看此文档:https://docs.frrouting.org/projects/dev-guide/en/latest/index.html FRR支持的协议众多…...

记录通过Cloudflare部署属于自己的docker镜像源
引言 由于最近国内无法正常拉取docker镜像,然而找了几个能用的docker镜像源发现拉取回来的docker镜像不是最新的版本,部署到Cloudflare里Workers 和 Pages,拉取docker 镜像成功,故记录部署过程。 部署服务 登录Cloudflare后&…...

波动方程 - 在三维图中动态显示二维波动方程的解就像水面波澜起伏
波动方程 - 在三维图中动态显示二维波动方程的解就像水面波澜起伏 flyfish 波动方程的求解结果通常不是一个单一的数值,而是一个函数或一组函数,这些函数描述了波随时间和空间的传播情况。具体来说,波动方程的解可以是关于时间和空间变量的…...

yum命令提示 错误:rpmdb: BDB0113 Thread/process 4153/139708200269632
一、报错信息 [rootDawn yum.repos.d]# yum clean all 错误:rpmdb: BDB0113 Thread/process 4153/139708200269632 failed: BDB1507 Thread died in Berkeley DB library 错误:db5 错误(-30973) 来自 dbenv->failchk:BDB0087 DB_RUNRECOVE…...

欢乐钓鱼大师游戏攻略:在什么地方掉称号鱼?云手机游戏辅助!
《欢乐钓鱼大师》是一款融合了休闲娱乐和策略挑战的钓鱼游戏。游戏中的各种鱼类不仅各具特色,而且钓鱼过程充满了挑战和乐趣。下面将为大家详细介绍如何在游戏中钓鱼,以及一些有效的钓鱼技巧,帮助你成为一个出色的钓鱼大师。 实用工具推荐 为…...

什么是构造函数?Java 中构造函数的重载如何实现?
构造函数,就像是建筑房屋时的奠基仪式,是Java类中一个特殊的方法,主要用于初始化新创建的对象。 每当创建一个类的新实例时,构造函数就会自动调用,负责为这个新对象分配内存,并对其进行必要的设置…...

Linux内核 -- ARMv7 与 ARMv8 中的 asmlinkage 作用及使用
ARMv7 与 ARMv8 中的 asmlinkage 作用及使用 asmlinkage 是一个宏,通常在内核代码中使用,用于定义调用约定,特别是指定函数的参数是通过栈传递而不是通过寄存器。它主要用于内核与汇编之间的接口函数,使得参数传递更加一致和明确…...

GPT提示词模板
BRTR 原则 # 背景(Background) - 描述任务的背景信息,包括任务的起因、目的、相关的历史信息或当前状况。 - 提供足够的背景信息以便让ChatGPT理解任务的上下文。 # 角色(Role) - 定义ChatGPT在任务中所扮演的角色&…...

WRF学习——使用CMIP6数据驱动WRF/基于ncl与vdo的CMIP6数据处理
动力降尺度 国际耦合模式比较计划(CMIP)为研究不同情景下的气候变化提供了大量的模拟数据,而在实际研究中,全球气候模式输出的数据空间分辨率往往较低(>100Km,缺乏区域气候特征,为了更好地研…...

机器人控制系列教程之Delta机器人动力学分析
动力学简介 机器人动力学分析是已知各运动构件的尺寸参数和惯性参数的情况下,求解末端运动状态与主驱动力矩之间的函数关系。 意义:对并联机器人动力学分析的意义体现在: 为伺服电机的选型提供理论依据;获得动力学参数为目标函数的最优问题做性能评价指标;为高精度控制提…...

VIM介绍
VIM(Vi IMproved)是一种高度可配置的文本编辑器,用于有效地创建和更改任何类型的文本。它是从 vi 编辑器发展而来的,后者最初是 UNIX 系统上的一个文本编辑器。VIM 以其键盘驱动的界面和强大的文本处理能力而闻名,是许…...

课设:选课管理系统(Java+MySQL)
在本博客中,我将介绍用Java、MySQL、JDBC和Swing GUI开发一个简单的选课管理系统。 技术栈 Java:用于编写应用程序逻辑MySQL:用于存储和管理数据JDBC:用于连接Java应用程序和MySQL数据库Swing GUI:用于构建桌面应用程…...

动态规划 剪绳子问题
给一段长度为n的绳子,请把绳子剪成m段,每段绳子的长度为k[0],k[1],k[2],k[3]....k[m].请问k[0]k[1]k[2].....*k[m]的最大乘积为多少 #include <vector> // 包含vector头文件 #include <algorithm> // 包含algorithm头文件,用于m…...

上位机图像处理和嵌入式模块部署(mcu项目1:实现协议)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 这种mcu的嵌入式模块理论上都是私有协议,因为上位机和下位机都是自己开发的,所以只需要自己保证上、下位机可以通讯上&…...

【NLP学习笔记】load_dataset加载数据
除了常见的load_dataset(<hf上的dataset名>)这种方式加载HF上的所有数据外,还有其他custom的选项。 加载HF上部分数据 from datasets import load_dataset c4_subset load_dataset("allenai/c4", data_files"en/c4-train.0000*-of-01024.js…...

企业如何选择好用的供应商管理系统
供应商管理系统软件(SRM)是企业用于管理供应链中各个供应商关系的重要工具。现如今竞争激烈的市场环境下,选择一款合适的SRM软件显得尤为重要。那么,如何选择一款好用的供应商管理系统呢? 企业在选择好用的供应商管理…...

震惊!运气竟能如此放大!运气的惊人作用,你了解吗?
芒格:得到你想要的东西,最保险的办法,就是让自己配得上你想要的那个东西。今天仔细想了想这句话,他其实说的是无数成功人士的心声 —— “我配得上!” 美剧《绝命毒师》有个导演叫文斯吉里根(Vince Gilliga…...

记录一次Apache Tomcat 处理返回自定义的404页面
记录工作中遇到处理访问tomcat 不存在的资源,返回自定义的404页面 删除webapps目录下的example、docs、manager、hta-manager目录,只保留 ROOT目录,应用部署在了这个目录 删除 manager、hta-manager 我没有发现有什么异常 制作404.jsp 或者 4…...

【piania 的用法】
piania 的用法 定义store建议使用箭头函数TypeScript插件扩展1、全局添加对象 定义store import { ref, computed } from vue import { defineStore } from pinia // pinia 以函数的形式暴露出去 export const useCounterStore defineStore(counter, () > {// 1、ref 相当…...