NetworkStream.ReadAsync带有消除令牌从来没有取消(NetworkStream.

2019-06-18 04:21发布

这里的证明。
任何想法是错误的代码?

    [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;
            }
        }
    }

Answer 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;
            }
        }
    }
}


Answer 2:

取消是合作。 NetworkStream.ReadAsync必须合作才能够被取消。 这是一种很难为它这样做,因为这将有可能离开流中的不确定状态。 什么字节已经从Windows TCP堆栈读,什么都没有? IO是不会轻易撤销。

反射显示NetworkStream不会覆盖ReadAsync 。 这意味着,它会得到的默认行为Stream.ReadAsync刚刚抛出令牌了。 有没有通用的方式流操作可以被取消,因此BCL Stream类甚至没有尝试(它不能尝试-有没有办法做到这一点)。

您应该设置在超时Socket



Answer 3:

每在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
    }
}


Answer 4:

有迹象表明,蹦出一些问题有:

  1. CancellationToken抛出OperationCanceledException ,不TimeoutException (取消并不总是由于超时)。
  2. ReceiveTimeout不适用,因为你正在做异步读取。 即使做了,你不得不之间的竞争条件IOExceptionOperationCanceledException
  3. 既然你同步连接插座,你要在这个测试高超时(IIRC,默认的连接超时是〜90秒,但与Windows监视网络的速度是可以改变的)。
  4. 测试异步代码的正确方法是用异步测试:

     [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()); } 


Answer 5:

我知道这是一个有点晚了,但是这是一个简单的事情,我通常做取消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&lt;int&gt; r = Stream.ReadAsync(reply, 0, reply.Length);

        //   This will throw a OperationCanceledException
        r.Wait(TimeOut.Token);
    }

}

编辑:我已经把在另一个Task ,以澄清。



文章来源: NetworkStream.ReadAsync with a cancellation token never cancels