如何让async Task日志方法在Release模式下成为空操作?
解决方案与分析
一、实现DEBUG模式执行、Release模式空操作(无Task开销)
完全可行,而且能彻底消除Release模式下的Task创建与销毁开销,核心思路是用条件编译+返回已完成Task的组合:
protected Task LogToProfiler(string message) { #if DEBUG return LogToProfilerImpl(message); #else // Release模式直接返回静态已完成Task,无任何额外开销 return Task.CompletedTask; #endif } // 仅DEBUG模式会编译这段实际逻辑 #if DEBUG private async Task LogToProfilerImpl(string message) { await using (var dbContext = await NoTrackingDbFactory.CreateDbContextAsync()) { await dbContext.Database.ExecuteSqlAsync($"-- {message}"); } } #endif
这么做的好处:
- Release模式下,
LogToProfiler直接返回.NET内置的Task.CompletedTask静态实例,不会创建新Task,也不会生成异步状态机,完全没额外性能损耗。 - 就算调用方写了
await LogToProfiler(...),在Release下也只是等待一个已经完成的Task,几乎没成本。
二、if (profilingOn)调用方式的开销与风险
当profilingOn为false时,确实不会执行方法调用,自然不会有Task开销,但存在开发者误操作的隐患:比如有人在Release模式下不小心把profilingOn设为true,或者在没加判断的地方直接调用方法,就会触发不必要的SQL操作和Task开销。
更安全的双重保障方案
如果想兼顾DEBUG模式的灵活性和Release模式的安全性,可以结合编译时条件和运行时开关:
// 编译时默认开关:DEBUG开,Release关 #if DEBUG private const bool DefaultProfilingState = true; #else private const bool DefaultProfilingState = false; #endif // 允许DEBUG模式下动态调整,Release模式下设置也没用 public bool ProfilingOn { get; set; } = DefaultProfilingState; protected Task LogToProfiler(string message) { // Release模式直接跳过,不管ProfilingOn的值 #if !DEBUG return Task.CompletedTask; #endif // DEBUG模式下再判断运行时开关 if (!ProfilingOn) return Task.CompletedTask; return LogToProfilerImpl(message); } #if DEBUG private async Task LogToProfilerImpl(string message) { await using (var dbContext = await NoTrackingDbFactory.CreateDbContextAsync()) { await dbContext.Database.ExecuteSqlAsync($"-- {message}"); } } #endif
这种方式:
- Release模式下,无论怎么设置
ProfilingOn,都不会执行实际逻辑,彻底杜绝误操作。 - DEBUG模式下可以通过
ProfilingOn动态开关日志,方便调试。
三、避坑提醒
- 别在Release模式下写空的async方法(比如
async Task { return; }),哪怕是空实现,编译器也会生成异步状态机,产生不必要的Task开销,必须直接返回Task.CompletedTask。 #if DEBUG是编译阶段生效的,Release模式下会直接剔除相关代码,不仅没运行时开销,还能减小程序集体积。
内容的提问来源于stack exchange,提问作者David Thielen
相关产品推荐
相关产品推荐

