ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

【进阶编程】 SemaphoreSlim异步锁

2026/9/5 6:58:30 拓冰建站 浏览量
【进阶编程】 SemaphoreSlim异步锁 如果你的需求是多个线程/任务都可能调用“读信号 / 写信号”但必须保证按调用顺序一个一个执行不能同时读写。在 C# 里推荐用SemaphoreSlim异步锁。1. 最简单的实现privatereadonlySemaphoreSlim_signalLocknewSemaphoreSlim(1,1);publicasyncTaskboolReadSignalAsync(stringaddress){await_signalLock.WaitAsync();try{// 实际读信号returnawaitReadSignalInternalAsync(address);}finally{_signalLock.Release();}}publicasyncTaskWriteSignalAsync(stringaddress,objectvalue){await_signalLock.WaitAsync();try{// 实际写信号awaitWriteSignalInternalAsync(address,value);}finally{_signalLock.Release();}}这样Task A - ReadSignalAsync ↓ 获取锁 ↓ 执行读取 ↓ 释放锁 Task B - WriteSignalAsync ↓ 等待锁 ↓ 获取锁 ↓ 执行写入不会出现Read Write Read Write同时执行的情况。2. 如果你要求“严格按照调用顺序”这里需要特别注意SemaphoreSlim适合保证互斥但不要把它当成严格 FIFO 队列来使用。例如ReadSignalAsync(A);WriteSignalAsync(B);ReadSignalAsync(C);WriteSignalAsync(D);如果你的要求是严格A ↓ B ↓ C ↓ D那么我更推荐直接做一个异步队列。方案Channel 串行执行privatereadonlyChannelFuncTask_signalQueueChannel.CreateUnboundedFuncTask();publicasyncTaskStartAsync(CancellationTokentoken){awaitforeach(varactionin_signalQueue.Reader.ReadAllAsync(token)){try{awaitaction();}catch(Exceptionex){// Log}}}读publicTaskboolReadSignalAsync(stringaddress){vartcsnewTaskCompletionSourcebool(TaskCreationOptions.RunContinuationsAsynchronously);_signalQueue.Writer.TryWrite(async(){try{varresultawaitReadSignalInternalAsync(address);tcs.SetResult(result);}catch(Exceptionex){tcs.SetException(ex);}});returntcs.Task;}写publicTaskWriteSignalAsync(stringaddress,objectvalue){vartcsnewTaskCompletionSourcebool(TaskCreationOptions.RunContinuationsAsynchronously);_signalQueue.Writer.TryWrite(async(){try{awaitWriteSignalInternalAsync(address,value);tcs.SetResult(true);}catch(Exceptionex){tcs.SetException(ex);}});returntcs.Task;}调用awaitReadSignalAsync(D100);awaitWriteSignalAsync(D200,100);awaitReadSignalAsync(D300);如果是不同线程同时调用线程1Read D100 线程2Write D200 线程3Read D300 线程4Write D400进入队列后┌──────────────┐ │ Read D100 │ ├──────────────┤ │ Write D200 │ ├──────────────┤ │ Read D300 │ ├──────────────┤ │ Write D400 │ └──────────────┘ ↓ 单线程 Worker ↓ 严格一个一个执行3. 如果你的“读写信号”是 PLC 通讯如果你现在做的是PLC Driver我实际上更推荐PLC Driver │ ├──ReadAsync()│ ├──WriteAsync()│ └──ExecuteAsync()│ ▼ Channel 请求队列 │ ▼ 单 Worker │ ▼ PLC通信而不是让每个ReadAsync/WriteAsync自己抢SemaphoreSlim。因为 PLC 通讯通常还需要解决读写严格顺序通讯超时单次请求异常PLC 断线自动重连CancellationToken批量读取写入后等待完成日志记录请求取消如果只是防止并发访问用SemaphoreSlim如果本质上是“所有 PLC 操作排队后依次执行”用Channel 单 Worker更合适。