【发布时间】:2023-08-09 04:49:01
【问题描述】:
我在玩TcpClient,当我使用一些异步操作时,它们会忽略 CancellationToken。经过一番阅读,我知道这是故意的,也知道存在一些取消异步操作等待的方法。
我刚刚阅读了下一个 * 问题和文章,阐明了一些要点:
How to cancel a Task in await?
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 运行时调用取消操作已经足够大了。
有人可以帮我解决这个问题吗?我花了几天时间试图解决这个问题,但无法找到合适的解决方案。
提前致谢。
【问题讨论】:
-
您需要重载
WithCancellation方法,该方法接受并返回简单的Task(非泛型类型)。 -
我很欣赏您的回答,但我不知道该怎么做。我已经尝试过,但无法实现正确的方法。如果你能用代码示例解释一下就好了。
-
Here 是此方法的一个更简单的实现,适用于
Task和Task<Result>类型。它被命名为AsCancelable而不是WithCancellation,但其他地方都是一样的。 -
顺便说一句,这些方法在每次调用时至少分配两个对象,因此它们不适合在热路径中使用(即不要每秒调用它们 100,000 次)!
-
感谢您的建议西奥多,我已经将它打包成一个任务,但没有成功。我将尝试构建一个控制台应用程序进行测试。
标签: c# asynchronous async-await cancellation