如何在异步上下文正确实现SemaphoreSlim?附日志操作代码示例
用SemaphoreSlim异步保护日志操作的正确方式
你的这段日志代码在多线程/异步场景下存在两个线程安全问题:一是多个线程同时写入日志文件会导致文件占用异常、日志内容错乱;二是全局变量ALL_LOGS的拼接操作非原子性,多线程修改会出现内容覆盖。SemaphoreSlim原生支持异步上下文,能完美解决这些问题,具体实现如下:
1. 定义全局唯一的SemaphoreSlim实例
在日志类中定义静态的SemaphoreSlim对象,初始计数设为1(保证同一时间仅一个线程能进入临界区):
private static readonly SemaphoreSlim _logSemaphore = new SemaphoreSlim(1, 1);
2. 异步保护临界区操作
将文件写入、ALL_LOGS拼接等线程不安全的操作,放到信号量保护范围内,用WaitAsync()异步等待信号量,并用try/finally确保信号量必被释放(即使操作出错):
// 方法需标记为async Task/async ValueTask以支持await public async Task WriteLogAsync(string log) { await _logSemaphore.WaitAsync(); try { if (!string.IsNullOrEmpty(LOGS_FILE) && File.Exists(LOGS_FILE)) { // 用异步写入进一步适配异步场景性能 using (StreamWriter streamWriter = new StreamWriter(LOGS_FILE, true)) { await streamWriter.WriteLineAsync(log); } } // 全局变量拼接需放入临界区,避免多线程覆盖 ALL_LOGS += log + Environment.NewLine; } finally { _logSemaphore.Release(); } // Debug.WriteLine本身线程安全,无需放入临界区 if (IsDebuggerAttached()) { System.Diagnostics.Debug.WriteLine(log); } }
关键注意事项
- 必须保证
SemaphoreSlim是全局唯一的(静态或单例实例),否则无法起到互斥作用 - 务必使用
WaitAsync()而非Wait(),前者适配异步上下文,不会阻塞线程池线程 - 所有涉及共享资源的操作都要放在
try块内,确保被信号量保护 finally块释放信号量是强制要求,否则会导致信号量被永久占用,后续线程无法进入临界区
内容的提问来源于stack exchange,提问作者ShaderCraft
相关产品推荐
相关产品推荐

