2016-01-21 4 views
1

Учитывая простой класс клиента сокета ниже, подключите его к TCP-серверу (я использую SocketTest3, свободно доступный в Интернете). Затем отключите сервер и подождите немного. Вы должны получить LockRecursionException.ReaderWriterLockSlim throws LockRecursionException с Socket.BeginReceive

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, как только получаю сообщение с нулевой длиной, но это больше упражнение. Я задавался вопросом, может ли подобная ошибка поддерживать постоянный поток обратных вызовов, работающих в фоновом режиме и краду ресурсов без явно плохих событий. Должен признаться, я не ожидал этого исключения.

Вопрос 1: Почему это происходит? Возможно, методы BeginXYZ позволяют выполнять обратные вызовы мгновенно, в том же потоке? Если это так, кто скажет, что этого не может быть во время нормальной работы?

Вопрос 2: Существуют ли способы избежать этого исключения, сохраняя при этом «желаемое» поведение в этом случае? Я имею в виду огонь без прерывания потока обратных вызовов.

Я использую Visual Studio 2010 с .NET 4.

+0

Я думаю, что вам не хватает вызова для клиента.EndReceive' в методе 'ReceiveCallback' (хотя это не отвечает на вопрос) –

+2

[источник ссылки] (http://referencesource.microsoft.com/#System/net/System/Net/Sockets/Socket.cs, 12174aa527fd9499), безусловно, указывает на то, что BeginReceive предназначен для выполнения в одном потоке: «Мы запускаем прием, и если он завершает синхронно, мы будем вызывать обратный вызов. В противном случае мы вернем IASyncResult, который вызывающий может использовать для ожидания или при необходимости получить окончательный статус ». Я ничего не вижу в документах или исходном коде, которые заставили бы меня подумать, что BeginReceive * требуется * для вызова обратного вызова в другом потоке. –

+0

Я не знал об этом. На самом деле это имеет смысл. С точки зрения производительности. Таким образом, это означает, что вы не можете использовать нерекурсивные блокировки, когда обратный вызов вызывает BeginReceive. Какая-то рекурсия, так что это имеет смысл. :) –

ответ

1

Вопрос 1: Почему это происходит? Возможно, методы BeginXYZ позволяют выполнять обратные вызовы мгновенно, в том же потоке? Если это так, кто скажет, что этого не может быть во время нормальной работы?

Как описано by mike z in the comments, метод BeginReceive() не требуется для выполнения асинхронно. Если данные доступны, то будет выполнять синхронно, вызывая делегат обратного вызова в том же потоке. Это по определению рекурсивный вызов и поэтому не будет совместим с нерекоммерческим объектом(например, ReaderWriterLockSlim, который вы используете здесь).

Это, безусловно, может произойти «во время нормальной работы». Я не уверен, что понимаю вторую часть вашего вопроса. Кто скажет, что этого не может быть? Никто. Это может произойдет.

Вопрос 2: Существуют ли способы избежать этого исключения, сохраняя при этом «желаемое» поведение в этом случае? Я имею в виду огонь без прерывания потока обратных вызовов.

Боюсь, что я также не знаю, что вы подразумеваете под «огнем беспрепятственный поток обратных вызовов».

Одним из очевидных способов решения проблемы является включение рекурсии на объект ReaderWriterLockSlim путем передачи LockRecursionPolicy.SupportsRecursion его конструктору. Кроме того, вы можете проверить свойство IsReadLockHeld, прежде чем пытаться выполнить блокировку.

Непонятно из вашего примера кода, почему у вас есть блокировка вообще, неважно, почему он используется таким образом. Возможно, правильное решение - не удерживать замок вообще, пока вы вызываете BeginReceive(). Используйте его только при обработке результата от EndReceive().