Как отменить NetworkStream.ReadAsync без закрытия потока

Я пытаюсь использовать NetworkStream.ReadAsync() для чтения данных, но я не могу найти, как отменить вызов ReadAsync(). Для фона NetworkStream предоставляется мне связанным объектом BluetoothClient (из библиотеки 32Feet.NET Bluetooth).

Текущий код get-it-working, который я пытаюсь, ниже.

int bytesRead;

while (this.continueReading)
{
    bytesRead = await this.stream.ReadAsync(this.buffer, 0, (int)this.buffer.Length);

    Console.WriteLine("Received {0} bytes", bytesRead);
}

Console.WriteLine("Receive loop has ended");

Код работает нормально, получая данные, и остановит цикл, если флаг continueReading установлен в false и данные получены, но пока данные не будут получены, он не будет проходить через строку ReadAsync(). Я не вижу, как отменить вызов без получения данных.

Я знаю, что существует перегрузка ReadAsync, которая предоставляет CancellationToken, но похоже, что поскольку NetworkStream не переопределяет поведение по умолчанию ReadAsync, токен игнорируется (см. NetworkStream.ReadAsync с маркером отмены никогда не отменяет).

Я попытался закрыть базовый поток, и это вызывает ожидающий вызов ReadAsync для вызова ObjectDisposedException, а базовое соединение Bluetooth также закрывается. В идеале я не хочу полностью разорвать соединение с устройством, чтобы прекратить чтение. Это не похоже на чистый способ сделать это, и вам не нужно разрывать весь поток, чтобы прервать вызов ReadAsync().

Любые советы?

Ответ 1

Вы можете реализовать асинхронную оболочку вокруг NetworkStream.Read(или ReadAsync), которая также получает уведомление об отмене, которое вы можете контролировать и соблюдать. Что-то вроде этого:

Task MyCancelableNetworkStreamReadAsync(NetworkStream stream, CancellationToken ct)
{
...
if(this.stream.CanRead)
{
  do 
  {
    //check ct.IsCancellationRequested and act as needed
    bytesRead = await this.stream.ReadAsync(this.buffer, 0, (int)this.buffer.Length);
  }
  while(myNetworkStream.DataAvailable);
}

Обратите внимание, что я просто пытаюсь проиллюстрировать эту идею, и вы можете wnt рассмотреть возможность возврата Task<TResult>, а также иметь ли цикл {} while, любую дополнительную обработку или очистку и т.д. - все согласно ваши потребности.

Я также хотел бы обратить ваше внимание на статью Стивена Туба Как отменить отмененные асинхронные операции? и расширение WithCancellation, которое он создает есть.

Ответ 2

Вы не можете отменить ReadAsync, поскольку внутренний вызов неуправляемый и использует порты IOCompletion. Ваши параметры следующие.

  • Используйте Socket.Shutdown(). Это вернет ReadAsync с ошибкой сокета OperationAborted.
  • Подождите, пока время ожидания чтения не будет.
  • Проверьте, доступны ли данные перед чтением из сокета.

Ответ 3

Я управляю отменой, ожидая, что Task вызовет токен отмены отмены между асинхронным вызовом и оператором ожидания следующим образом:

try {

 ....

 Task<int> readTask = input.ReadAsync(buffer, 0, buffer.Length);
 readTask.Wait(myCancellationToken);
 int readBytes= await readTask;

....

}
catch ( OperationCanceledException e )
{
   // handle cancellation
}