这里的证明。
任何想法是错误的代码?
[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;
}
}
}
我终于找到了解决办法。 结合使用Task.WaitAny延迟任务(Task.Delay)异步调用。 当延迟的IO任务逝去前,关闭流。 这将强制任务停止。 你应该正确处理的IO任务异步例外。 你应该添加后续任务同时为延迟任务和IO任务。
它还与TCP连接工作。 关闭在另一个线程的连接(你可以认为它是延迟任务线程)使用/等待就此停止强制所有异步任务。
- 编辑 -
通过@vtortola建议另一个清洁的解决方案:使用取消标记注册到stream.Close呼叫:
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
必须合作才能够被取消。 这是一种很难为它这样做,因为这将有可能离开流中的不确定状态。 什么字节已经从Windows TCP堆栈读,什么都没有? IO是不会轻易撤销。
反射显示NetworkStream
不会覆盖ReadAsync
。 这意味着,它会得到的默认行为Stream.ReadAsync
刚刚抛出令牌了。 有没有通用的方式流操作可以被取消,因此BCL Stream
类甚至没有尝试(它不能尝试-有没有办法做到这一点)。
您应该设置在超时Socket
。
每在Softlion的回答说明:
结合使用Task.WaitAny延迟任务(Task.Delay)异步调用。 当延迟的IO任务逝去前,关闭流。 这将强制任务停止。 你应该正确处理的IO任务异步例外。 你应该添加后续任务的同时迪利任务和IO任务。
我做了一些代码,为您提供了超时的异步读:
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
}
}
我知道这是一个有点晚了,但是这是一个简单的事情,我通常做取消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);
}
}
编辑:我已经把在另一个Task
,以澄清。