I'm playing with TcpClient
and when i use some Async operations they ignore the CancellationToken. After some reading, i know that it is intentionally and also knows that exists some ways to cancel awaits on Asyncs operations.
I just read next StackOverflow questions and articles that clarifies some points:
How to cancel a Task in await?
https://devblogs.microsoft.com/pfxteam/how-do-i-cancel-non-cancelable-async-operations/
Following previous articles, I could cancel NetworkStream.ReadAsync
but that mechanisms doesn't work when i use them on NetworkStream.WriteAsync
.
I have this code as minimal example:
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();
}
}
It works good when i use it on ReadAsync:
await tcp_stream.ReadAsync(response, 0, response.Length, ct).WithCancellation(ct);
It doesn't work when i use it on WriteAsync:
await tcp_stream.WriteAsync(header_bytes, 0, header_bytes.Length, ct).WithCancellation(ct);
No error is returned, simply the await isn't cancelled. To be more clear i added a minimal example as a Visual Studio 2015 project that you can download here: https://github.com/Zeokat/minimal_ex/archive/master.zip
It also includes a file 4test.rar
that you can decompress into a file of 39MB 4test.txt
. I use this text file as post_data
contents for test because is big enougth to call the Cancel action while the WriteAsync
is running.
Can someone give me a hand on this? I spend some days trying to fix this but couldn't achieve a proper solution.
Thanks in advance.