ReaderWriterLockSlim使用Socket.BeginReceive抛出LockRecursionException

时间:2016-01-21 18:07:47

标签: c# sockets callback readerwriterlockslim

鉴于下面的简单套接字客户端类,将其连接到TCP服务器(我使用SocketTest3,可在线免费获得)。然后断开服务器并等待一下。你应该得到LockRecursionException

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Net;
using System.Net.Sockets;

using System.Threading;

namespace SocketRwlTest
{
    public class SocketRwlTest
    {
        private Socket client = new Socket(AddressFamily.InterNetwork,
                                           SocketType.Stream,
                                           ProtocolType.Tcp);
        private readonly ReaderWriterLockSlim rwl = new ReaderWriterLockSlim();
        private const int maxLength = 200;

        public SocketRwlTest(IPAddress address, ushort port)
        {
            client.Connect(new IPEndPoint(address, port));
            ReceiveOne();
        }

        private void ReceiveOne()
        {
            rwl.EnterReadLock();
            try
            {
                var inArray = new byte[maxLength];
                client.BeginReceive(inArray, 0, maxLength, 0,
                                    new AsyncCallback(ReceivedCallback),
                                    inArray);
            }
            finally
            {
                rwl.ExitReadLock();
            }
        }

        private void ReceivedCallback(IAsyncResult ar)
        {
            client.EndReceive(ar);
            ReceiveOne();
        }
    }
}

我不明白为什么它会在给出的简化示例中发生。我知道我应该在收到零长度消息后立即停止呼叫ReceiveOne,但这更像是一种练习。我想知道类似的bug是否能够在后台运行不断的回调流并窃取资源而不会发生明显不好的事情。我必须承认我并不期待这个特殊例外。

问题1:为什么会发生这种情况?是否允许BeginXYZ方法在同一个线程上立即执行回调?如果是这样的话,谁说这在正常运行时间内不会发生?

问题2:是否有办法避免在保持"期望"在这种情况下的行为?我的意思是发射一连串不停的回调。

我使用Visual Studio 2010和.NET 4。

1 个答案:

答案 0 :(得分:1)

  

问题1:为什么会发生这种情况?是否允许BeginXYZ方法在同一个线程上立即执行回调?如果是这种情况,谁说这在正常运行时间不会发生?

正如by mike z in the comments所述,BeginReceive()方法不是必需以异步方式执行。如果数据可用,同步执行,在同一个线程中调用回调委托。根据定义,这是一个递归调用,因此与使用非递归锁定对象(例如您在此处使用的ReaderWriterLockSlim)不兼容。

这肯定会在“正常运行时”发生。我不确定我理解你问题的第二部分。谁说它不可能发生?没有人。 可以发生。

  

问题2:在这种情况下,是否有办法避免出现此异常,同时仍保持“理想”行为?我的意思是发射一连串不停的回调。

我恐怕我也不知道你的意思是“发出一连串不停的回调”。

一个明显的解决方法是通过将ReaderWriterLockSlim传递给其构造函数来启用LockRecursionPolicy.SupportsRecursion对象的递归。或者,您可以在尝试锁定之前检查IsReadLockHeld属性。

从您的代码示例中不清楚为什么您完全拥有锁,更不用说为什么以特定方式使用它。正确的解决方案是在您拨打BeginReceive()时根本不能保持锁定。仅在处理EndReceive()的结果时使用它。