如何反向绘制出 .NET程序 异步方法调用栈
一:背景
1. 讲故事
这个问题源于给训练营里的一位朋友分析的卡死dump,在分析期间我需要知道某一个异步方法的调用栈,但程序是 .framework 4.8 ,没有sos后续版本独有的 !dumpasync 命令,所以这就比较搞了,但转念一想,既然 !dumpasync 能把调用栈搞出来,按理说我也可以给他捞出来,所以就有了此篇。
二:异步调用栈研究
1. 一个简单的案例
为了模拟的真实一点,搞一个简单的三层架构,最后在 DAL 层的 ReadAsync 之后给它断住,参考代码如下:
namespace Example_18_1_1.UI
{
internal class Program
{
static void Main(string[] args)
{
Task.Run(() =>
{
var task = GetCustomersAsync();
Console.WriteLine(task.IsCompleted);
});
Console.ReadLine();
}
static async Task GetCustomersAsync()
{
string connectionString = @"Server=(localdb)\MyInstance;Database=MyDatabase;Integrated Security=true;";
try
{
Console.WriteLine("Starting async database query...");
// 初始化服务
var customerService = new CustomerService(connectionString);
// 获取并显示客户数据
var customers = await customerService.GetCustomersForDisplayAsync();
foreach (var customer in customers)
{
Console.WriteLine($"Customer: ID={customer.Id}, Name={customer.Name}");
}
Console.WriteLine("Query completed successfully.");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
}
namespace Example_18_1_1.BLL
{
public class CustomerService
{
private readonly CustomerRepository _repository;
public CustomerService(string connectionString)
{
_repository = new CustomerRepository(connectionString);
}
public async Task<IEnumerable<Customer>> GetCustomersForDisplayAsync()
{
// 这里可以添加业务逻辑,如验证、转换等
var customers = await _repository.GetTop10CustomersAsync();
// 示例业务逻辑:确保名称不为null
foreach (var customer in customers)
{
customer.Name ??= "Unknown";
}
return customers;
}
}
}
namespace Example_18_1_1.DAL
{
public class CustomerRepository
{
private readonly string _connectionString;
public CustomerRepository(string connectionString)
{
_connectionString = connectionString;
}
public async Task<IEnumerable<Customer>> GetTop10CustomersAsync()
{
var customers = new List<Customer>();
await using (var connection = new SqlConnection(_connectionString))
{
await connection.OpenAsync();
var command = new SqlCommand("SELECT TOP 10 * FROM Customers", connection);
await using (var reader = await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
customers.Add(new Customer
{
Id = Convert.ToInt32(reader["Id"]),
Name = Convert.ToString(reader["Name"])
});
Debugger.Break();
}
}
}
return customers;
}
}
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
}
从代码流程看,异步调用链是这样的 GetCustomersAsync -> GetCustomersForDisplayAsync -> GetTop10CustomersAsync 一个过程,在程序中断之后,我们用 WinDbg 附加,使用 !clrstack 观察当前调用栈。
0:017> !clrstack
OS Thread Id: 0x3118 (17)
Child SP IP Call Site
000000ABD6CBEAF8 00007ffeb1e61db2 [HelperMethodFrame: 000000abd6cbeaf8] System.Diagnostics.Debugger.BreakInternal()
000000ABD6CBEC00 00007ffdf818a91a System.Diagnostics.Debugger.Break() [/_/src/coreclr/System.Private.CoreLib/src/System/Diagnostics/Debugger.cs @ 18]
000000ABD6CBEC30 00007ffd9915079d Example_18_1_1.DAL.CustomerRepository+d__2.MoveNext() [D:\skyfly\18.20220727\src\Example\Example_18_1_1\Program.cs @ 115]
000000ABD6CBEE50 00007ffdf827f455 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.__Canon, System.Private.CoreLib],[System.__Canon, System.Private.CoreLib]].ExecutionContextCallback(System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs @ 286]
000000ABD6CBEE80 00007ffdf808dde9 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/ExecutionContext.cs @ 183]
000000ABD6CBEEF0 00007ffdf827f593 System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.__Canon, System.Private.CoreLib],[System.__Canon, System.Private.CoreLib]].MoveNext(System.Threading.Thread) [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs @ 324]
000000ABD6CBEF60 00007ffdf827f4ec System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.__Canon, System.Private.CoreLib],[System.__Canon, System.Private.CoreLib]].MoveNext() [/_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncTaskMethodBuilderT.cs @ 302]
000000ABD6CBEF90 00007ffdf80a9a06 System.Threading.Tasks.AwaitTaskContinuation.RunOrScheduleAction(System.Runtime.CompilerServices.IAsyncStateMachineBox, Boolean) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/TaskContinuation.cs @ 795]
000000ABD6CBEFF0 00007ffdf80a48eb System.Threading.Tasks.Task.RunContinuations(System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 3374]
000000ABD6CBF0D0 00007ffdf80a4866 System.Threading.Tasks.Task.FinishContinuations() [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 3350]
000000ABD6CBF110 00007ffdf8251350 System.Threading.Tasks.Task`1[[System.__Canon, System.Private.CoreLib]].TrySetResult(System.__Canon) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Future.cs @ 400]
000000ABD6CBF160 00007ffdf8254fc3 System.Threading.Tasks.UnwrapPromise`1[[System.__Canon, System.Private.CoreLib]].TrySetFromTask(System.Threading.Tasks.Task, Boolean)
000000ABD6CBF1C0 00007ffdf825515b System.Threading.Tasks.UnwrapPromise`1[[System.__Canon, System.Private.CoreLib]].ProcessInnerTask(System.Threading.Tasks.Task) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 6940]
000000ABD6CBF200 00007ffdf8254ead System.Threading.Tasks.UnwrapPromise`1[[System.__Canon, System.Private.CoreLib]].ProcessCompletedOuterTask(System.Threading.Tasks.Task)
000000ABD6CBF240 00007ffdf8254d1b System.Threading.Tasks.UnwrapPromise`1[[System.__Canon, System.Private.CoreLib]].Invoke(System.Threading.Tasks.Task) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 6802]
000000ABD6CBF280 00007ffdf80a4e11 System.Threading.Tasks.Task.RunOrQueueCompletionAction(System.Threading.Tasks.ITaskCompletionAction, Boolean)
000000ABD6CBF2C0 00007ffdf80a4c0a System.Threading.Tasks.Task.RunContinuations(System.Object) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 3392]
000000ABD6CBF3A0 00007ffdf80a4866 System.Threading.Tasks.Task.FinishContinuations() [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 3350]
000000ABD6CBF3E0 00007ffdf80a2e9f System.Threading.Tasks.Task.FinishStageThree() [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2125]
000000ABD6CBF410 00007ffdf80a2d0b System.Threading.Tasks.Task.FinishStageTwo() [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2095]
000000ABD6CBF460 00007ffdf80a33f6 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef, System.Threading.Thread) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2350]
000000ABD6CBF500 00007ffdf80a3293 System.Threading.Tasks.Task.ExecuteEntryUnsafe(System.Threading.Thread) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2271]
000000ABD6CBF540 00007ffdf80a323a System.Threading.Tasks.Task.ExecuteFromThreadPool(System.Threading.Thread) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Tasks/Task.cs @ 2262]
000000ABD6CBF570 00007ffdf80969df System.Threading.ThreadPoolWorkQueue.Dispatch()
000000ABD6CBF610 00007ffdf809e566 System.Threading.PortableThreadPool+WorkerThread.WorkerThreadStart() [/_/src/libraries/System.Private.CoreLib/src/System/Threading/PortableThreadPool.WorkerThread.cs @ 107]
000000ABD6CBF730 00007ffdf8082f0f System.Threading.Thread.StartCallback() [/_/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @ 105]
000000ABD6CBF9C0 00007ffdf8ccbde3 [DebuggerU2MCatchHandlerFrame: 000000abd6cbf9c0]
卦中真的是眼花缭乱,找瞎了眼也没找到调用链上的三个方法名,只有一个 Example_18_1_1.DAL.CustomerRepository+d__2 状态机类,经过 ILSpy反编译才能勉强的看到是 GetTop10CustomersAsync 方法,截图如下:

所以sos为了让调试者免去这个痛苦,新增了 !dumpasync 命令。
0:017> !dumpasync
STACK 1
0000028b00029338 00007ffd993d1e00 (-1) Example_18_1_1.DAL.CustomerRepository+<GetTop10CustomersAsync>d__2 @ 7ffd991502a0
0000028b00029438 00007ffd993d3290 (0) Example_18_1_1.BLL.CustomerService+<GetCustomersForDisplayAsync>d__2 @ 7ffd9914d6c0
0000028b00029550 00007ffd993d3fe8 (0) Example_18_1_1.UI.Program+<GetCustomersAsync>d__1 @ 7ffd9914b8f0
虽然能以 屏蔽外部代码 的方式显示出了异步调用栈,但这个sos 命令是 .netcore 独有的,所以作为高级调试者,我们必须具有手工绘制的能力。
2. 如何手工绘制
要想手工绘制,需要了解异步状态机的内部机制,即子函数和父函数是通过 m_continuationObject 字段串联的,去年我写过一篇关于异步方法串联的文章,可以参考下 (https://www.cnblogs.com/huangxincheng/p/18662162)[聊一聊 C#异步 任务延续的三种底层玩法],这里就不具体说了,用一张图来表示吧。

本质上来说就是 Box 之间形成了一个跨线程的由m_continuationObject串联出的单链表,有了思路之后,我们开始验证吧,使用 !dso 找到头节点 box。
0:017> !dso
OS Thread Id: 0x3118 (17)
SP/REG Object Name
rbx 028b00029338 System.Runtime.CompilerServices.AsyncTaskMethodBuilder<System.Collections.Generic.IEnumerable<Example_18_1_1.DAL.Customer>>+AsyncStateMachineBox<Example_18_1_1.DAL.CustomerRepository+<GetTop10CustomersAsync>d__2>
....
0:017> !dumpobj /d 28b00029338
Name: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Collections.Generic.IEnumerable`1[[Example_18_1_1.DAL.Customer, Example_18_1_1]], System.Private.CoreLib],[Example_18_1_1.DAL.CustomerRepository+<GetTop10CustomersAsync>d__2, Example_18_1_1]]
Fields:
MT Field Offset Type VT Attr Value Name
...
00007ffd99125690 4000db9 20 System.Object 0 instance 0000028b00029438 m_continuationObject
...
0:017> !DumpObj /d 0000028b00029438
Name: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Collections.Generic.IEnumerable`1[[Example_18_1_1.DAL.Customer, Example_18_1_1]], System.Private.CoreLib],[Example_18_1_1.BLL.CustomerService+<GetCustomersForDisplayAsync>d__2, Example_18_1_1]]
Fields:
MT Field Offset Type VT Attr Value Name
...
00007ffd99125690 4000db9 20 System.Object 0 instance 0000028b00029550 m_continuationObject
...
0:017> !DumpObj /d 0000028b00029550
Name: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Example_18_1_1.UI.Program+<GetCustomersAsync>d__1, Example_18_1_1]]
Fields:
MT Field Offset Type VT Attr Value Name
...
00007ffd99125690 4000db9 20 System.Object 0 instance 0000000000000000 m_continuationObject
...
00007ffd99125708 4001337 48 System.__Canon 0 instance 0000028b0000e7f8 StateMachine
...
上面三个 m_continuationObject 值即是 !dumpasync 输出的结果,最后一个 m_continuationObject=null 说明为异步执行流的最后一个节点,流程正在这里没出来,可以把这个异步状态机给解包出来,即卦中的 StateMachine 字段,输出如下:
0:017> !do 0000028b0000e7f8
Name: Example_18_1_1.BLL.CustomerService+<GetCustomersForDisplayAsync>d__2
Fields:
MT Field Offset Type VT Attr Value Name
00007ffd991c94b0 4000018 30 System.Int32 1 instance 0 <>1__state
00007ffd9924fca0 4000019 38 ...Private.CoreLib]] 1 instance 0000028b0000e830 <>t__builder
00007ffd99247298 400001a 8 ...L.CustomerService 0 instance 0000028b0000e7c8 <>4__this
00007ffd992453b0 400001b 10 ... Example_18_1_1]] 0 instance 0000000000000000 <customers>5__1
00007ffd992453b0 400001c 18 ... Example_18_1_1]] 0 instance 0000000000000000 <>s__2
00007ffd99246d60 400001d 20 ... Example_18_1_1]] 0 instance 0000000000000000 <>s__3
00007ffd99245338 400001e 28 ..._1_1.DAL.Customer 0 instance 0000000000000000 <customer>5__4
00007ffd99245448 400001f 40 ...Private.CoreLib]] 1 instance 0000028b0000e838 <>u__1
再配上 ILSpy 反编译出来的状态机代码,截图如下:

可以根据这里面的字段赋值情况来推测当前正执行哪一个阶段。
3. 父节点如何找到子节点
刚才我们是通过 子节点 -> 父节点 寻找法,在真实的dump分析中,可能还会存在反向的情况,即 父节点 -> 子节点 寻找法,但父节点寻找目标子节点的过程中会存在多条链路,比如 GetTop10CustomersAsync 方法中存在五个 await 就对应着 4条链路。

用状态机的话术就是下面的4个 <>u__xxxx。

可能有些朋友还是有点懵,没关系,我也绘制一张图。

最后通过 windbg 来验证一下。
0:017> !do 0000028b00029550
Name: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib],[Example_18_1_1.UI.Program+<GetCustomersAsync>d__1, Example_18_1_1]]
Fields:
MT Field Offset Type VT Attr Value Name
00007ffd99125708 4001337 48 System.__Canon 0 instance 0000028b0000de10 StateMachine
0:017> !DumpObj /d 0000028b0000de10
Name: Example_18_1_1.UI.Program+<GetCustomersAsync>d__1
Fields:
MT Field Offset Type VT Attr Value Name
00007ffd99245448 400002b 50 ...Private.CoreLib]] 1 instance 0000028b0000de60 <>u__1
0:017> !DumpVC /d 00007ffd99245448 0000028b0000de60
Name: System.Runtime.CompilerServices.TaskAwaiter`1[[System.Collections.Generic.IEnumerable`1[[Example_18_1_1.DAL.Customer, Example_18_1_1]], System.Private.CoreLib]]
Fields:
MT Field Offset Type VT Attr Value Name
00007ffd99247db8 400139e 0 ...Private.CoreLib]] 0 instance 0000028b00029438 m_task
0:017> !DumpObj /d 0000028b00029438
Name: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Collections.Generic.IEnumerable`1[[Example_18_1_1.DAL.Customer, Example_18_1_1]], System.Private.CoreLib],[Example_18_1_1.BLL.CustomerService+<GetCustomersForDisplayAsync>d__2, Example_18_1_1]]
Fields:
MT Field Offset Type VT Attr Value Name
00007ffd99125708 4001337 48 System.__Canon 0 instance 0000028b0000e7f8 StateMachine
0:017> !DumpObj /d 0000028b0000e7f8
Name: Example_18_1_1.BLL.CustomerService+<GetCustomersForDisplayAsync>d__2
00007ffd99245448 400001f 40 ...Private.CoreLib]] 1 instance 0000028b0000e838 <>u__1
0:017> !DumpVC /d 00007ffd99245448 0000028b0000e838
Name: System.Runtime.CompilerServices.TaskAwaiter`1[[System.Collections.Generic.IEnumerable`1[[Example_18_1_1.DAL.Customer, Example_18_1_1]], System.Private.CoreLib]]
Fields:
MT Field Offset Type VT Attr Value Name
00007ffd99247db8 400139e 0 ...Private.CoreLib]] 0 instance 0000028b00029338 m_task
0:017> !DumpObj /d 0000028b00029338
Name: System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1+AsyncStateMachineBox`1[[System.Collections.Generic.IEnumerable`1[[Example_18_1_1.DAL.Customer, Example_18_1_1]], System.Private.CoreLib],[Example_18_1_1.DAL.CustomerRepository+<GetTop10CustomersAsync>d__2, Example_18_1_1]]
MethodTable: 00007ffd993d1e00
EEClass: 00007ffd993c1810
Tracked Type: false
Size: 96(0x60) bytes
File: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\6.0.36\System.Private.CoreLib.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007ffd99125708 4001337 48 System.__Canon 0 instance 0000028b0000e870 StateMachine
0:017> !DumpObj /d 0000028b0000e870
Name: Example_18_1_1.DAL.CustomerRepository+<GetTop10CustomersAsync>d__2
Fields:
MT Field Offset Type VT Attr Value Name
...
00007ffd992602f0 4000014 60 ...vices.TaskAwaiter 1 instance 0000028b0000e8d0 <>u__1
00007ffd99267a60 4000015 68 ....Data.SqlClient]] 1 instance 0000028b0000e8d8 <>u__2
00007ffd99260450 4000016 70 ...Private.CoreLib]] 1 instance 0000028b0000e8e0 <>u__3
00007ffd99260ae8 4000017 78 ....ValueTaskAwaiter 1 instance 0000028b0000e8e8 <>u__4
4. 有没有更快捷的方式
手工绘制虽然是兜底方案,但每次都要这样搞也确实太累,所以最近我在思考有没有更好的方式,好巧不巧,昨天在知乎上刷到了这样的一篇文章,hez2010大佬的话突然点醒了我,截图如下:

哈哈,点醒了我什么呢?即 sos 解析托管代码的能力远不如官方的 Visual Studio,毕竟后者才是全球最专业的托管代码调试器,将生成好的dump丢到 VS 中,在 Stack 或者 Parallel Stack 中一定要屏蔽 外部代码(External Code) ,否则海量的 AsyncTaskMethodBuilder 和 MoveNext 会淹死我们,截图如下:

三:总结
手工绘制异步调用栈需要对异步的底层构建有一个清晰的认识,调试师是痛苦的,要想进阶为资深,需要日积月累的底层知识沉淀,在自我学习的过程中如果没有无数次的在绝望中寻找希望的能力,很容易从入门到放弃。。。

如何反向绘制出 .NET程序 异步方法调用栈的更多相关文章
- Asp反向代理程序,调用远程站点全站数据,一款脚本级反向代理程序.
前些天临时写的一脚本级反向代理程序,用法很简单,设置好目标站地址,然后放到你网站根目录:index.asp,再将404页面自定义为:index.asp,即可. 由于暂时没有 url 替换需要,所以没有 ...
- 使用 Visual Studio 分析器找出应用程序瓶颈(转)
使用 Visual Studio 分析器找出应用程序瓶颈 Hari Pulapaka and Boris Vidolov 本文讨论: 以性能瓶颈为目标 应用程序代码分析 比较分析数据 性能报告 本文使 ...
- 使用 Visual Studio 分析器找出应用程序瓶颈
VS的性能分析工具 性能分析工具的选择 打开一个“性能分析”的会话:Debug->Start Diagnotic Tools Without Debugging(或按Alt+F2),VS2013 ...
- [AIR] AIR 应用程序的调用和终止
本节讨论几种对已安装的 Adobe® AIR® 应用程序进行调用的方法,以及关闭运行中的应用程序的选项和注意事项. 注: NativeApplication.InvokeEvent 和 Browser ...
- 在c或c+程序里打印调用栈。转
在C/C++程序里打印调用栈信息 我们知道,GDB的backtrace命令可以查看堆栈信息.但很多时候,GDB根本用不上.比如说,在线上环境中可能没有GDB,即使有,也不太可能让我们直接在上面调试.如 ...
- 在C/C++程序里打印调用栈信息
我们知道,GDB的backtrace命令可以查看堆栈信息.但很多时候,GDB根本用不上.比如说,在线上环境中可能没有GDB,即使有,也不太可能让我们直接在上面调试.如果能让程序自己输出调用栈,那是最好 ...
- 发布在IIS上的Web程序,调用服务器的COM组件
场景大致是这样的,在工厂中分布着许多的PDA点,这些PDA点都要进行实时的扫描--打印操作.实现方法是采用网络打印机,然后服务器安装驱动,管理着所有的打印机.然后服务器,发布一个WebService, ...
- asyn4j -- java 异步方法调用框架
asyn4j 是一个java异步方法调用框架,基于消费者与生产者模式.包括了异步方法执行,异步回调执行,异步工作缓存模块.支持Spring. 让我们写异步方法不再写很多的相关多线程代码.用asyn4j ...
- Native Application 开发详解(直接在程序中调用 ntdll.dll 中的 Native API,有内存小、速度快、安全、API丰富等8大优点)
文章目录: 1. 引子: 2. Native Application Demo 展示: 3. Native Application 简介: 4. Native Ap ...
- Cocos2d-x--Box2D绘制出两个矩形框的解决方案
一个简单的Demo,只是在程序窗口绘制出一个矩形 找到以下代码,注释掉其中一句 效果:
随机推荐
- Convert byte array to short array in C#
Create the short array at half the size of the byte array, and copy the byte data in: short[] sdata ...
- Python if分支
分支语句: 1.单分支语句 1 if 条件表达式: 2 print("如果成立怎么养怎么样") 例:让用户输入年龄,如果大于18就可以去网吧偷耳机age = int(input(& ...
- Typecho自定义右键菜单美化和禁用F12
右键美化 使用右键美化,请禁用 HoerMouse 鼠标美化插件,否则貌似没效果 Joe主题在后台-外观设置-设置外观-全局设置-自定义<body></body>标签内填入如下 ...
- MybatisPlus - [04] 分页
limit m,n.PageHelper.MyBatisPlus分页插件 001 || MybatisPlus分页插件 (1)引入maven依赖 <dependency> <grou ...
- 使用mybatis-plus转换枚举值
1. 使用mybatis-plus转换枚举值 枚举值转换方式有很多,有以下方式: 后端写一个通用方法,只要前端传枚举类型,后端返回相应的枚举值前端去匹配 优点:能够实时保持数据一致性 缺点:如果有大量 ...
- 【目标检测】一、初始的R-CNN与SVM
1.流程 为什么要用SVM而不是CNN最后一层的softmax? 取什么模型必然是有标准衡量,这个流程取得是书上[4]写的,作者说他得实验证明SVM比FC的mAP要高,所以我流程暂且这样画了. R-C ...
- 【ABAQUS2023-Output Vars】使用记录
计算结构的应变能,ALLSE=所有单元的ESEDEN*EVOL.但这不适用于模态分析,因为模态分析EVOL不能用 ALLSE Field: no History: yes .fil: automati ...
- 基于近红外与可见光双目摄像头的活体人脸检测,文末附Demo
基于近红外与可见光双目摄像头的活体人脸检测原理 人脸活体检测(Face Anti-Spoofing)是人脸识别系统中的重要一环,它负责验证捕捉到的人脸是否为真实活体,以抵御各种伪造攻击,如彩色纸张打印 ...
- 【CF VP记录】Codeforces Round 1008 (Div. 2)
比赛链接 本文原文发布于博客园,如您在其他平台刷到此文,请前往博客园获得更好的阅读体验. 跳转链接:https://www.cnblogs.com/TianTianChaoFangDe/p/18766 ...
- 质数测试——Fermat素数测试和MillerRabin素数测试
质数测试 今天我来填坑了,之前我在数学基础算法--质数篇这篇文章中提到我要单独讲一下MillerRabin算法,最近已经有许多粉丝在催了,所以我马不停蹄的来出这篇文章了,顺便把Fermat素数测试也讲 ...