MSMQ 异步异常行为 - .NET 4.0 与 .NET 2.0
最近在MSMQ中遇到了异步操作的问题。在 .NET 2.0、3.0 和 3.5 中,如果存在挂起的异步接收,并且队列被删除,则会调用回调,并且在调用 EndReceive 时会引发异常。
在 .NET 4.0 中,永远不会调用回调,但可以通过 AppDomain.UnhandledException 事件处理程序捕获异常。在调试器中运行时,应用程序将直接终止,而 Visual Studio 不会发出发生异常的通知。
此代码在 64 位 Windows 7 Professional 上执行。但是,无论应用程序面向 x86 还是 x64,行为都是相同的。 (编辑:也在 XP SP3 32 位上验证了此行为 - 这似乎是一个框架错误,与操作系统无关)
我假设此新行为与 .NET 4.0 完全相关新的运行时。我不确定此时该怎么做,但本质上我希望恢复 .NET 4.0 之前的行为,同时仍然针对 .NET 4.0 运行时。任何帮助或建议将不胜感激。下面是重现该问题的示例代码:
class Program
{
static void Main( string[] args )
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler( CurrentDomain_UnhandledException );
string path = @".\private$\mytestqueue";
// Create queue only if it doesn't already exist.
var queue = MessageQueue.Exists( path ) ? new MessageQueue( path ) : MessageQueue.Create( path );
queue.BeginReceive( TimeSpan.FromSeconds( 15 ), queue, new AsyncCallback( ReceiveComplete ) );
Thread.Sleep( 5000 );
MessageQueue.Delete( path );
}
static void CurrentDomain_UnhandledException( object sender, UnhandledExceptionEventArgs e )
{
var mqEx = (MessageQueueException) e.ExceptionObject;
// .NET 4.0:
// "The queue does not exist or you do not have sufficient
// permissions to perform the operation."
Console.WriteLine( mqEx.Message );
// "QueueNotFound"
Console.WriteLine( mqEx.MessageQueueErrorCode );
}
static void ReceiveComplete( IAsyncResult ar )
{
// This callback is never invoked under .NET 4.0.
Console.WriteLine( "Finishing Receive." );
var queue = (MessageQueue) ar.AsyncState;
try
{
queue.EndReceive( ar );
}
catch ( MessageQueueException mqEx )
{
// .NET 2.0 through 3.5:
// "Queue handle can no longer be used to receive messages
// because the queue was deleted. The handle should be closed."
Console.WriteLine( mqEx.Message );
// "QueueDeleted"
Console.WriteLine( mqEx.MessageQueueErrorCode );
}
}
}
附录:
在花费太多时间尝试使用源代码步进之后(System.Messaging 源代码适用于 4.0,但不适用于 2.0/3.5),并且使用 Reflector 搜索两个不同的 System.Messaging 程序集,我终于找到了问题。
在 2.0 程序集中,MessageQueue.AsynchronousRequest.RaiseCompletionEvent 方法中使用了一些 try/catch 块来捕获异常并存储错误代码,以便在调用 .EndReceive() 时引发异常。然而,在 4.0 程序集中,这些 try/catch 已经消失,因此当发生异常时,进程必须终止,因为它们在后台线程上未被捕获。
不幸的是,这并不能帮助我解决问题。我正在考虑切换到同步接收,但我喜欢利用 I/O 完成端口来实现这一点。
I recently encountered a problem with asynchronous operations in MSMQ. In .NET 2.0, 3.0 and 3.5, if there is a pending asynchronous receive, and the queue is deleted, the callback is invoked and upon calling EndReceive, the exception is thrown.
In .NET 4.0, the callback is never invoked, but the exception can be caught by the AppDomain.UnhandledException event handler. When running in the debugger, the application will simply terminate with no notification from Visual Studio that an exception occurred.
This code is executing on Windows 7 Professional, 64-bit. However the behavior is the same whether the application is targeting x86 or x64. (Edit: verified this behavior on XP SP3 32-bit as well - this appears to be a framework bug, not OS-related)
I am assuming this new behavior is related to .NET 4.0 being a completely new runtime. I'm not sure what to do at this point, but essentially I am looking to get the pre-.NET 4.0 behavior back, while still targeting the .NET 4.0 runtime. Any help or advice would be greatly appreciated. Here is sample code to reproduce the problem:
class Program
{
static void Main( string[] args )
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler( CurrentDomain_UnhandledException );
string path = @".\private$\mytestqueue";
// Create queue only if it doesn't already exist.
var queue = MessageQueue.Exists( path ) ? new MessageQueue( path ) : MessageQueue.Create( path );
queue.BeginReceive( TimeSpan.FromSeconds( 15 ), queue, new AsyncCallback( ReceiveComplete ) );
Thread.Sleep( 5000 );
MessageQueue.Delete( path );
}
static void CurrentDomain_UnhandledException( object sender, UnhandledExceptionEventArgs e )
{
var mqEx = (MessageQueueException) e.ExceptionObject;
// .NET 4.0:
// "The queue does not exist or you do not have sufficient
// permissions to perform the operation."
Console.WriteLine( mqEx.Message );
// "QueueNotFound"
Console.WriteLine( mqEx.MessageQueueErrorCode );
}
static void ReceiveComplete( IAsyncResult ar )
{
// This callback is never invoked under .NET 4.0.
Console.WriteLine( "Finishing Receive." );
var queue = (MessageQueue) ar.AsyncState;
try
{
queue.EndReceive( ar );
}
catch ( MessageQueueException mqEx )
{
// .NET 2.0 through 3.5:
// "Queue handle can no longer be used to receive messages
// because the queue was deleted. The handle should be closed."
Console.WriteLine( mqEx.Message );
// "QueueDeleted"
Console.WriteLine( mqEx.MessageQueueErrorCode );
}
}
}
Addendum:
After spending way too much time trying to use source stepping (System.Messaging source is available for 4.0 but not for 2.0/3.5, it appears), and hunting through the two different System.Messaging assemblies with Reflector, I finally found the problem.
In the 2.0 assembly, some try/catch blocks are used in the MessageQueue.AsynchronousRequest.RaiseCompletionEvent method to catch exceptions and store an error code so that the exception can be raised when .EndReceive() is called. However, in the 4.0 assembly, these try/catches are gone, so when an exception occurs the process must terminate since they are un-caught on a background thread.
Unfortunately this doesn't help me fix the problem. I am considering switching to a synchronous Receive, but I liked the idea of taking advantage of I/O completion ports for this.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
好吧,我将回答这个问题并接受它,因为我认为这是近期最好的答案。可能需要几个月(或更长时间)才能找到适当的解决方案。
如上所述,我在 Microsoft Connect 上提交了错误报告,因此几乎需要他们将行为恢复到 CLR 2.0 中的工作方式。
Microsoft Connect:http://connect.microsoft。 com/VisualStudio/feedback/details/626177/messagequeue-beginreceive-asynchronous-exception-behavior
至于这如何影响我的应用程序,我不愿意切换到同步 Receive 方法,因为这会消耗所有线程池上可用的工作线程。我的应用程序经常创建和删除大量队列,当发出删除队列的命令但未完成的读取操作处于待处理状态时,就会出现此问题。相反,我只会标记需要删除的队列,并且一旦经过一段安全时间(例如,BeginReceive 超时的两倍),我将实际删除该队列。
或者切换到与 MSMQ 不同的排队系统,尽管到目前为止我对此很满意。
Well, I am going to answer this and accept it, since I think it's the best answer for the near future. It could be months (or more) before there is a proper solution.
As mentioned above, I filed a bug report on Microsoft Connect, so it is pretty much up to them to revert the behavior to how it worked in CLR 2.0.
Microsoft Connect: http://connect.microsoft.com/VisualStudio/feedback/details/626177/messagequeue-beginreceive-asynchronous-exception-behavior
As far as how this affects my application, I am not willing to switch to a synchronous Receive method, as that would consume all of the available worker threads on the thread pool. My application frequently creates and removes a lot of queues, and this issue arose when a command to remove a queue was issued, but an outstanding read operation was pending. Instead, I will just mark that a queue needs to be removed, and once a safe period of time has elapsed (two times the BeginReceive timeout, for instance), I will actually remove the queue.
Or switch to a different queuing system than MSMQ, though I've been happy with it so far.