MSMQ队列计数获取时抛出System.InvalidOperationException异常求助
排查MSMQ性能计数器实例不存在的异常问题
咱们先拆解下这个异常的核心原因——你遇到的System.InvalidOperationException: Instance 'xxxxx\private$\xxx_queue' does not exist in the specified Category,在使用MSMQ性能计数器时特别常见,尤其是队列刚创建或者消息被清空的场景:
异常原因分析
- 性能计数器实例的延迟/销毁机制:MSMQ的性能计数器实例不是和队列绑定终身的。当队列刚创建还没产生任何消息活动时,系统不会立即初始化对应的计数器实例;当队列消息被清空后,为了节省资源,系统可能会销毁这个计数器实例,直到有新消息进入才会重新创建。这就导致你在队列空的时候去访问计数器,会触发实例不存在的异常。
- 计数器实例名称的潜在格式问题:虽然你的异常信息里实例名看起来是对的,但私有队列的名称在性能计数器中有时会有特殊解析逻辑,不过这个在你的场景里概率较低,主要还是上面的延迟/销毁问题。
解决办法
下面给你几个实用的解决方案,按可行性和优先级排序:
1. 捕获异常并重试(最推荐)
既然计数器实例的创建/恢复需要一点时间,那我们可以在捕获到特定异常时进行重试,最多重试2-3次,每次间隔几百毫秒,重试失败就默认返回0(毕竟此时队列大概率是空的)。修改你的代码如下:
public JObject GetQueueItemsCount() { JObject returnObject = new JObject(); int queueCount = 0; var queueName = ConfigurationManager.AppSettings["QueueName"]; var machineName = ConfigurationManager.AppSettings["MachineName"]; int retryCount = 3; int retryDelayMs = 200; while (retryCount > 0) { try { if (MessageQueue.Exists(queueName)) { using (var queueCounter = new PerformanceCounter("MSMQ Queue", "Messages in Queue", queueName, machineName)) { queueCount = (int)queueCounter.NextValue(); // 若第一次取值不稳定,可取消注释再取一次 // queueCount = (int)queueCounter.NextValue(); } } break; // 成功获取,跳出循环 } catch (InvalidOperationException ex) { if (ex.Message.Contains("Instance does not exist") && retryCount > 1) { retryCount--; Thread.Sleep(retryDelayMs); continue; } throw; // 非目标异常或重试耗尽,抛出原异常 } catch (Exception ex) { throw; } } returnObject.Add("queueCount", queueCount); return returnObject; }
2. 先检查计数器实例是否存在
在创建PerformanceCounter之前,先通过PerformanceCounterCategory.InstanceExists方法检查实例是否存在,不存在就直接返回0:
public JObject GetQueueItemsCount() { JObject returnObject = new JObject(); int queueCount = 0; var queueName = ConfigurationManager.AppSettings["QueueName"]; var machineName = ConfigurationManager.AppSettings["MachineName"]; try { if (MessageQueue.Exists(queueName)) { var category = new PerformanceCounterCategory("MSMQ Queue", machineName); if (category.InstanceExists(queueName)) { using (var queueCounter = new PerformanceCounter("MSMQ Queue", "Messages in Queue", queueName, machineName)) { queueCount = (int)queueCounter.NextValue(); } } // 实例不存在时,默认队列计数为0 } } catch (Exception ex) { throw ex; } finally { returnObject.Add("queueCount", queueCount); } return returnObject; }
3. 备选方案:不使用性能计数器(适合小队列)
如果上面的方案还是有问题,你可以改用MessageQueue.GetAllMessages().Length来获取队列计数,但注意这个方法会枚举队列里的所有消息,当队列消息量很大时,性能会很差,只适合消息量少的场景:
public JObject GetQueueItemsCount() { JObject returnObject = new JObject(); int queueCount = 0; var queueName = ConfigurationManager.AppSettings["QueueName"]; try { if (MessageQueue.Exists(queueName)) { using (var messageQueue = new MessageQueue(queueName)) { // 注意:这个方法效率低,消息多的时候慎用 queueCount = messageQueue.GetAllMessages().Length; } } } catch (Exception ex) { throw ex; } finally { returnObject.Add("queueCount", queueCount); } return returnObject; }
4. 提前初始化计数器实例
如果你是在程序启动时创建新队列,可以先往队列里发送一条测试消息,然后立即删除它,这样MSMQ就会初始化对应的性能计数器实例,后续即使队列空了,计数器实例也可能会保留更久(不过系统还是可能回收,只能降低异常概率)。
内容的提问来源于stack exchange,提问作者Thili
相关产品推荐
相关产品推荐

