Замена Socket.ReceiveAsync на NetworkStream.ReadAsync (доступно) - PullRequest
10 голосов
/ 16 марта 2012

У меня есть приложение, которое одновременно создает пару сотен TCP-соединений и получает от них постоянный поток данных.

 private void startReceive()
    {
        SocketAsyncEventArgs e = new SocketAsyncEventArgs();
        e.Completed += receiveCompleted;
        e.SetBuffer(new byte[1024], 0, 1024);
        if (!Socket.ReceiveAsync(e)) { receiveCompleted(this, e); }  
    }

    void receiveCompleted(object sender, SocketAsyncEventArgs e)
    {
        ProcessData(e);

        if (!Socket.ReceiveAsync(e)) { receiveCompleted(this, e); }
    }

Мои попытки привели к чему-то вроде этого:

private async void StartReceive()
    {
        byte[] Buff = new byte[1024];
        int recv = 0;
        while (Socket.Connected)
        {
            recv = await NetworkStream.ReadAsync(Buff, 0, 1024);
            ProcessData(Buff,recv);
        }
    }

Проблема, с которой я столкнулся, заключалась в том, что вызов метода StartReceive() заблокировал бы, а не попал в сопровождающий StartSend() method called after StartReceive () . Creating a new task for StartReceive () would just end up with 300-ish threads, and it seems to do so just by calling StartReceive () `в любом случае.

Каков будет правильный способ реализации новых ключевых слов async и await в моем существующем коде при использовании NetworkStream, поэтому он использует пул потоков, который используются Socket.SendAsync() и Socket.ReceiveAsync(), чтобы избежатьиметь сотни потоков / задач?

Есть ли какое-либо преимущество в производительности при использовании networkstream таким образом по сравнению с портами завершения ввода / вывода с beginreceive?

1 Ответ

25 голосов
/ 17 марта 2012

Здесь вы меняете сразу две вещи: асинхронный стиль (SocketAsyncEventArgs до Task / async) и уровень абстракции (Socket до NetworkStream).

Поскольку вы уже знакомы с Socket, я рекомендую просто изменить асинхронный стиль и продолжить использование класса Socket напрямую.

Async CTP не дает Socket любой async -совместимые методы (что странно; я предполагаю, что они были опущены по ошибке и будут добавлены в .NET 4.5).

Не так сложно создать свой собственный метод расширения ReceiveAsyncTask (и аналогичные оболочки для другихоперации), если вы используете мою библиотеку AsyncEx :

public static Task<int> ReceiveAsyncTask(this Socket socket,
    byte[] buffer, int offset, int size)
{
  return AsyncFactory<int>.FromApm(socket.BeginReceive, socket.EndReceive,
      buffer, offset, size, SocketFlags.None);
}

Как только вы это сделаете, ваш StartReceive можно записать так:

private async Task StartReceive()
{
  try
  {
    var buffer = new byte[1024];
    while (true)
    {
      var bytesReceived = await socket.ReceiveAsyncTask(buffer, 0, 1024)
          .ConfigureAwait(false);
      ProcessData(buffer, bytesReceived);
    }
  }
  catch (Exception ex)
  {
    // Handle errors here
  }
}

Теперь,чтобы решить множество второстепенных вопросов:

  • await не создает новую тему.Я написал async / await intro в своем блоге , как и многие другие.async / await допускает параллелизм, но это не обязательно подразумевает многопоточность.
  • Сотни потоков могут быть проблематичными.Сотни задач , тем не менее, не являются проблемой;пул потоков и BCL предназначены для выполнения множества задач.
  • async / await не является новой формой асинхронной обработки;это просто более простой способ до экспресс асинхронной обработки.Это все еще использует IOCPs внизу.async / await имеет немного более низкую производительность, чем методы более низкого уровня;Его привлекательность заключается в простоте написания и составления асинхронных методов.
  • Очень занятая система может видеть некоторое повышенное давление ГХ, когда она переключается на async / await.Стивен Тауб из Parallel Team написал несколько примеров ожидаемых сокетов , которые могут помочь с этой проблемой.(Я рекомендую сначала использовать простой шаблон и использовать подход с повышенной производительностью только в том случае, если вы считаете это необходимым; тем не менее, полезно знать, что он существует, если он вам в итоге понадобится).
  • Асинхронные методы должныверните Task, если вам действительно не нужно их вернуть void.Task ожидаемо, поэтому ваш метод является составным (и более легко тестируемым);void больше похоже на «запустить и забыть».
  • Вы можете вызвать ConfigureAwait(false), чтобы сообщить остальному методу async о выполнении в потоке пула потоков.Я использую это в моем примере выше, так что ProcessData выполняется в потоке пула потоков, так же как это было при использовании SocketAsyncEventArgs.
  • Socket.Connected бесполезно. Вам необходимо отправить данные, чтобы определить, является ли соединение действующим.
...