单线程应用使用MSMQ ReceiveCompleted异步接收是否会触发多线程导致重复插入?
结论
你当前的代码实现不会出现多线程并行处理多条消息的问题,你担忧的并行触发场景在现有逻辑下不会发生。
原因说明
- MSMQ的
BeginReceive方法为单次异步接收请求:你当前代码里,下一次的BeginReceive()调用放在了QueueReceived回调的最后一行,也就是必须等当前消息的全部处理逻辑(包含数据库插入操作ProcessBXRS)执行完成后,才会发起下一次消息监听请求。哪怕队列中堆积了大量消息,也只会等前一条处理完成后才会接收下一条,不会同时触发多个回调。 ReceiveCompleted事件的回调确实由线程池线程调度,但由于你同一时间只会发起一个异步接收请求,因此同一时间只会有一个回调线程在运行,不存在并行处理的情况。
仍需注意的隐患
虽然不会有并行处理问题,但仍有可能出现重复插入的情况,常见场景如下:
- 消息处理完成后、下一次
BeginReceive调用前进程崩溃:如果你的MSMQ队列配置为可恢复模式/事务性队列,未被确认接收的消息会重回队列,进程重启后会再次处理同一条消息,导致重复插入。 - 若后续调整代码时误将
BeginReceive()的调用位置提前到ProcessBXRS执行之前,会立刻发起下一次接收请求,进而出现并行处理的情况,导致你担忧的查重逻辑失效问题。
优化建议
- 数据库层面加唯一约束:这是最稳妥的防重手段,哪怕应用层逻辑出现漏洞,数据库也会直接拦截重复插入请求,不会产生脏数据,可靠性远高于应用层的先查询后插入逻辑。
- 保持现有
BeginReceive的调用顺序:不要调整回调中BeginReceive的位置,必须等业务逻辑全部执行完成后再发起下一次监听,即可严格保证串行处理。 - 使用事务性队列接收:如果要避免消息丢失同时避免重复处理,可以开启MSMQ的事务性接收,业务逻辑执行成功后再提交事务,处理失败则回滚事务让消息重回队列,配合数据库唯一约束即可同时保证可靠性和数据一致性。
事务性接收的参考实现如下:
Dim objQueue As New MessageQueue(ConfigurationManager.AppSettings("myQueuePath")) AddHandler objQueue.ReceiveCompleted, AddressOf QueueReceived objQueue.BeginReceive() Private Sub QueueReceived(source As Object, asyncResult As ReceiveCompletedEventArgs) Dim mq = DirectCast(source, MessageQueue) Dim objMessage As Message = Nothing ' 声明MSMQ事务 Dim mqTran As New MessageQueueTransaction() Try mqTran.Begin() mq.Formatter = New XmlMessageFormatter(New [String]() {"System.String,mscorlib"}) ' 从事务中结束异步接收 objMessage = mq.EndReceive(asyncResult.AsyncResult, mqTran) Dim strMessage As String = objMessage.Body.ToString() '调用例程读取数据,检查重复后插入数据库 ProcessBXRS(objMessage) ' 业务处理成功后提交MSMQ事务,消息才会从队列中移除 mqTran.Commit() Catch ex As Exception ' 异常回滚事务,消息重回队列 mqTran.Abort() '执行异常处理逻辑 End Try '监听下一条消息 mq.BeginReceive() End Sub
内容的提问来源于stack exchange,提问作者David P
相关产品推荐
相关产品推荐

