首页 > 解决方案 > 如何使用 NetworkStream.WriteAsync 取消等待?

问题描述

我正在玩,TcpClient当我使用一些异步操作时,它们会忽略 CancellationToken。经过一番阅读,我知道这是故意的,也知道存在一些取消异步操作等待的方法。

我刚刚阅读了下一个 StackOverflow 问题和文章,其中阐明了一些要点:

如何取消等待中的任务?

https://devblogs.microsoft.com/pfxteam/how-do-i-cancel-non-cancelable-async-operations/

按照之前的文章,我可以取消NetworkStream.ReadAsync,但是当我在NetworkStream.WriteAsync.

我有这个代码作为最小的例子:

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        CancellationTokenSource ctSource;
        private async void button1_Click(object sender, EventArgs e)
        {
            button1.Enabled = false;
            string http_resp = "";
            var request_uri = new Uri("http://icanhazip.com");

            //BIG FILE as postdata to test the WriteAsync (use one that you have on your disk)
            string contents = File.ReadAllText(@"C:\Portables\4test.txt");
            string post_data = contents;

            ctSource = new CancellationTokenSource();
            CancellationToken ct = ctSource.Token;

            Task<string> task = HttpRequestAsync(post_data, request_uri, ct);
            try
            {
                http_resp = await task;
            }
            catch
            {
                http_resp = "General error";
            }

            textBox1.Text = http_resp;

            button1.Enabled = true;
        }

        private static async Task<string> HttpRequestAsync(string post_data, Uri request_uri, CancellationToken ct)
        {
            string result = string.Empty;
            string http_method = "POST";
            string post_content_type = "application/x-www-form-urlencoded";

            var hostname = request_uri.Host;
            var port = request_uri.Port;
            var scheme = request_uri.Scheme;

            using (TcpClient tcpClient = new TcpClient())
            {
                tcpClient.SendTimeout = 15;
                tcpClient.ReceiveTimeout = 15;
                try
                {
                    await tcpClient.ConnectAsync(hostname, port);
                }
                catch (Exception d1)
                {
                    if (ct.IsCancellationRequested)
                    {
                        result = "Cancelation requested on ConnectAsync";
                    }
                    else
                    {
                        result = d1.Message + "\r\n" + d1.GetType().FullName + d1.StackTrace; ;
                    }
                    return result;
                }


                //Build HTTP headers
                string reqString = "";
                string header_host = "Host: " + hostname + "\r\n";
                string header_close = "Connection: Close\r\n";
                string basic_headers = "User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0\r\n";
                basic_headers += "Referer: https://www.google.com\r\n";

                string header_post = "";
                if (http_method == "POST")
                {
                    string header_content_type = "";
                    header_content_type = "Content-type: " + post_content_type + "\r\n";
                    int content_length = 0;
                    content_length = post_data.Length;
                    string header_content_length = "Content-length: " + content_length + "\r\n";
                    header_post = header_content_type + header_content_length;
                }

                reqString = http_method + " " + request_uri.PathAndQuery + " " + "HTTP/1.1" + "\r\n" + header_host + basic_headers + header_close + header_post + "\r\n";
                if (http_method == "POST")
                {
                    reqString += post_data;
                }
                var header_bytes = Encoding.ASCII.GetBytes(reqString.ToString());

                //Starting the I/O Network operations
                using (NetworkStream tcp_stream = tcpClient.GetStream())
                {
                    try
                    {
                        //HERE is where i have problems cancelling this await while WriteAsync is working.
                        await tcp_stream.WriteAsync(header_bytes, 0, header_bytes.Length, ct).WithCancellation(ct);
                        //await tcp_stream.WriteAsync(header_bytes, 0, header_bytes.Length, ct);
                    }
                    catch (Exception d2)
                    {
                        if (ct.IsCancellationRequested)
                        {
                            result = "Cancelation requested on WriteAsync";
                        }
                        else
                        {
                            result = d2.Message + "\r\n" + d2.GetType().FullName + d2.StackTrace;
                        }
                        return result;
                    }

                    using (var memory = new MemoryStream())
                    {
                        try
                        {
                            await tcp_stream.CopyToAsync(memory, 81920, ct);
                        }
                        catch (Exception d3)
                        {
                            if (ct.IsCancellationRequested)
                            {
                                result = "Request cancelled by user (on read)";
                            }
                            else
                            {
                                result = d3.Message + "\r\n" + d3.GetType().FullName + d3.StackTrace;
                            }
                            return result;
                        }
                        memory.Position = 0;
                        byte[] data = memory.ToArray();
                        result = Encoding.UTF8.GetString(data);
                    }
                }
            }
            return result;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            ctSource.Cancel();
        }
    }

当我在 ReadAsync 上使用它时效果很好:

await tcp_stream.ReadAsync(response, 0, response.Length, ct).WithCancellation(ct);

当我在 WriteAsync 上使用它时它不起作用

await tcp_stream.WriteAsync(header_bytes, 0, header_bytes.Length, ct).WithCancellation(ct);

没有返回错误,只是没有取消等待。为了更清楚,我添加了一个最小示例作为 Visual Studio 2015 项目,您可以在此处下载:https ://github.com/Zeokat/minimal_ex/archive/master.zip

它还包括一个文件4test.rar,您可以将其解压缩为 39MB 的文件4test.txt。我使用这个文本文件作为post_data测试的内容,因为在运行时调用取消操作足够大WriteAsync

有人可以帮我解决这个问题吗?我花了几天时间试图解决这个问题,但无法找到合适的解决方案。

提前致谢。

标签: c#asynchronousasync-awaitcancellation

解决方案


不要使用 .WithCancellation(ct) 仅使用 await tcp_stream.WriteAsync(header_bytes, 0, header_bytes.Length, ct)。

cts = 新的 CancellationTokenSource();

通过 ct = cts.Token

在 cancel_event() 中: if(cts != null) cts.Cancel();


推荐阅读