Объекте networkstream.ReadAsync с токеном отмены никогда не отменяет
вот доказательство.
Есть идеи, что не так в этом коде ?
[TestMethod]
public void TestTest()
{
var tcp = new TcpClient() { ReceiveTimeout = 5000, SendTimeout = 20000 };
tcp.Connect(IPAddress.Parse("176.31.100.115"), 25);
bool ok = Read(tcp.GetStream()).Wait(30000);
Assert.IsTrue(ok);
}
async Task Read(NetworkStream stream)
{
using (var cancellationTokenSource = new CancellationTokenSource(5000))
{
int receivedCount;
try
{
var buffer = new byte[1000];
receivedCount = await stream.ReadAsync(buffer, 0, 1000, cancellationTokenSource.Token);
}
catch (TimeoutException e)
{
receivedCount = -1;
}
}
}
5 ответов
Я, наконец, нашел обходной путь. Объедините асинхронный вызов с задачей задержки (Task.Задержка) использование задачи.WaitAny. Когда задержка истекает перед задачей ввода-вывода, закройте поток. Это заставит задачу остановиться. Вы должны правильно обработать асинхронное исключение в задаче ввода-вывода. И вы должны добавить задачу продолжения как для отложенной задачи, так и для задачи ввода-вывода.
Он также работает с tcp-соединениями. Закрытие соединения в другом потоке (вы можете считать, что это поток задачи задержки) заставляет все асинхронные задачи, использующие/ожидающие остановки этого соединения.
-- EDIT--
другое более чистое решение, предложенное @vtortola: используйте токен отмены для регистрации вызова stream.Закрыть:
async Task Read(NetworkStream stream)
{
using (var cancellationTokenSource = new CancellationTokenSource(5000))
{
using(cancellationTokenSource.Token.Register(() => stream.Close()))
{
int receivedCount;
try
{
var buffer = new byte[1000];
receivedCount = await stream.ReadAsync(buffer, 0, 1000, cancellationTokenSource.Token);
}
catch (TimeoutException e)
{
receivedCount = -1;
}
}
}
}
отмена кооператива. NetworkStream.ReadAsync
должны сотрудничать, чтобы иметь возможность быть отменены. Это довольно сложно сделать, потому что это потенциально оставит поток в неопределенном состоянии. Какие байты уже были прочитаны из стека TCP Windows, а какие нет? IO не легко отменить.
рефлектор показывает, что NetworkStream
не перекроет ReadAsync
. Это означает, что он получит поведение по умолчанию Stream.ReadAsync
который просто выбрасывает токен. Нет общего способ потоковых операций может быть отменен, поэтому BCL Stream
класс даже не пытается (он не может попробовать - нет способа сделать это).
вы должны установить тайм-аут на Socket
.
согласно описанию в ответе Softlion:
объединить асинхронный вызов с задачей задержки (Task.Задержка) использование задачи.WaitAny. Когда задержка истекает перед задачей ввода-вывода, закройте поток. Это заставит задачу остановиться. Вы должны правильно обработать асинхронное исключение в задаче ввода-вывода. И вы должны добавить задачу продолжения как для задачи dealy, так и для задачи ввода-вывода.
Я сделал код, который дает вам асинхронное чтение с тайм-аут:
using System;
using System.Net.Sockets;
using System.Threading.Tasks;
namespace ConsoleApplication2013
{
class Program
{
/// <summary>
/// Does an async read on the supplied NetworkStream and will timeout after the specified milliseconds.
/// </summary>
/// <param name="ns">NetworkStream object on which to do the ReadAsync</param>
/// <param name="s">Socket associated with ns (needed to close to abort the ReadAsync task if the timeout occurs)</param>
/// <param name="timeoutMillis">number of milliseconds to wait for the read to complete before timing out</param>
/// <param name="buffer"> The buffer to write the data into</param>
/// <param name="offset">The byte offset in buffer at which to begin writing data from the stream</param>
/// <param name="amountToRead">The maximum number of bytes to read</param>
/// <returns>
/// a Tuple where Item1 is true if the ReadAsync completed, and false if the timeout occurred,
/// and Item2 is set to the amount of data that was read when Item1 is true
/// </returns>
public static async Task<Tuple<bool, int>> ReadWithTimeoutAsync(NetworkStream ns, Socket s, int timeoutMillis, byte[] buffer, int offset, int amountToRead)
{
Task<int> readTask = ns.ReadAsync(buffer, offset, amountToRead);
Task timeoutTask = Task.Delay(timeoutMillis);
int amountRead = 0;
bool result = await Task.Factory.ContinueWhenAny<bool>(new Task[] { readTask, timeoutTask }, (completedTask) =>
{
if (completedTask == timeoutTask) //the timeout task was the first to complete
{
//close the socket (unless you set ownsSocket parameter to true in the NetworkStream constructor, closing the network stream alone was not enough to cause the readTask to get an exception)
s.Close();
return false; //indicate that a timeout occurred
}
else //the readTask completed
{
amountRead = readTask.Result;
return true;
}
});
return new Tuple<bool, int>(result, amountRead);
}
#region sample usage
static void Main(string[] args)
{
Program p = new Program();
Task.WaitAll(p.RunAsync());
}
public async Task RunAsync()
{
Socket s = new Socket(SocketType.Stream, ProtocolType.Tcp);
Console.WriteLine("Connecting...");
s.Connect("127.0.0.1", 7894); //for a simple server to test the timeout, run "ncat -l 127.0.0.1 7894"
Console.WriteLine("Connected!");
NetworkStream ns = new NetworkStream(s);
byte[] buffer = new byte[1024];
Task<Tuple<bool, int>> readWithTimeoutTask = Program.ReadWithTimeoutAsync(ns, s, 3000, buffer, 0, 1024);
Console.WriteLine("Read task created");
Tuple<bool, int> result = await readWithTimeoutTask;
Console.WriteLine("readWithTimeoutTask is complete!");
Console.WriteLine("Read succeeded without timeout? " + result.Item1 + "; Amount read=" + result.Item2);
}
#endregion
}
}
есть несколько проблем, которые вылезут:
-
CancellationToken
закидываемOperationCanceledException
, а неTimeoutException
(отмена не всегда происходит из-за таймаута). -
ReceiveTimeout
не применяется, так как вы выполняете асинхронное чтение. Даже если бы это было так, у вас было бы состояние гонки междуIOException
иOperationCanceledException
. - поскольку вы синхронно подключаете сокет, вам понадобится высокий тайм-аут в этом тесте (IIRC, тайм-аут соединения по умолчанию составляет ~90 секунд, но может быть изменено, поскольку Windows контролирует скорость сети).
-
правильный способ проверки асинхронного кода - асинхронный тест:
[TestMethod] public async Task TestTest() { var tcp = new TcpClient() { ReceiveTimeout = 5000, SendTimeout = 20000 }; tcp.Connect(IPAddress.Parse("176.31.100.115"), 25); await Read(tcp.GetStream()); }
Я знаю, что немного поздно, но это простая вещь, которую я обычно делаю, чтобы отменить ReadAsync()
(в моем случае: NetworkStream) (протестировано):
Task.Run(() =>
{
// This will create a new CancellationTokenSource, that will cancel itself after 30 seconds
using (CancellationTokenSource TimeOut = new CancellationTokenSource(30*1000))
{
Task<int> r = Stream.ReadAsync(reply, 0, reply.Length);
// This will throw a OperationCanceledException
r.Wait(TimeOut.Token);
}
}
Edit: Я положил это в другой Task
, чтобы уточнить.