Async/await with CancellationToken doesn't cancel the operation - c#

I want to use the CancellationToken to abort a file download. This is what I tried:
public async Task retrieveDocument(Document document)
{
// do some preparation work first before retrieving the document (not shown here)
if (cancelToken == null)
{
cancelToken = new CancellationTokenSource();
try
{
Document documentResult = await webservice.GetDocumentAsync(document.Id, cancelToken.Token);
// do some other stuff (checks ...)
}
catch (OperationCanceledException)
{
Console.WriteLine("abort download");
}
finally
{
cancelToken = null;
}
}
else
{
cancelToken.Cancel();
cancelToken = null;
}
}
public async Task<Document> GetDocumentAsync(string documentId, CancellationToken cancelToken)
{
Document documentResult = new Document();
try
{
cancelToken.ThrowIfCancellationRequested();
documentResult = await Task.Run(() => manager.GetDocumentById(documentId));
}
return documentResult;
}
The cancelToken should then be used to cancel the operation:
public override void DidReceiveMemoryWarning ()
{
// Releases the view if it doesn't have a superview.
base.DidReceiveMemoryWarning ();
if (cancelToken != null) {
Console.WriteLine ("Token cancelled");
cancelToken.Cancel ();
}
}
It seems that IsCancellationRequested is not updated. So the operation is not cancelled. I also tried to use this
cancelToken.ThrowIfCancellationRequested();
try{
documentResult = await Task.Run(() => manager.GetDocumentById (documentId), cancelToken);
} catch(TaskCanceledException){
Console.WriteLine("task canceled here");
}
but nothing changed.
What I'm doing wrong?
Edit:
Here are the missing parts like GetDocumentById:
public Document GetDocumentById(string docid)
{
GetDocumentByIdResult res;
try
{
res = ws.CallGetDocumentById(session, docid);
}
catch (WebException e)
{
throw new NoResponseFromServerException(e.Message);
}
return res;
}
public Document CallGetDocumentById(Session session, string parmsstring)
{
XmlDocument soapEnvelope = Factory.GetGetDocumentById(parmsstring);
HttpWebRequest webRequest = CreateWebRequest(session);
webRequest = InsertEnvelope(soapEnvelope, webRequest);
string result = WsGetResponseString(webRequest);
return ParseDocument(result);
}
static string WsGetResponseString(WebRequest webreq)
{
string soapResult = "";
IAsyncResult asyncResult = webreq.BeginGetResponse(null, null);
if (asyncResult.AsyncWaitHandle.WaitOne(50000))
{
using (WebResponse webResponse = webreq.EndGetResponse(asyncResult))
{
if (webResponse != null)
{
using (var rd = new StreamReader(webResponse.GetResponseStream()))
{
soapResult = rd.ReadToEnd();
}
}
}
}
else
{
webreq.Abort();
throw new NoResponseFromServerException();
}
return soapResult;
}

I want to use the CancellationToken to abort a file download
Downloading a file is an I/O operation, for which asynchronous cancelable (I/O completion port based) functions are available on the .NET platform. Yet you seem to not be using them.
Instead you appear to be creating (a chain of) tasks using Task.Run that perform blocking I/O, where a cancelation token is not passed on to each task in your Task.Run chain.
For examples of doing async, awaitable and cancelable file downloads, refer to:
Using HttpClient: How to copy HttpContent async and cancelable?
Windows Phone:
Downloading and saving a file Async in Windows Phone 8
Using WebClient: Has its own cancellation mechanism: the CancelAsync method, you can connect it to your cancellation token, using the token's Register method:
myToken.Register(myWebclient.CancelAsync);
Using the abstract WebRequest: If it was not created using an attached cancelation token, as seems to be the case for your edited example, and you are not actually downloading a file, but reading a content string, you need to use a combination of a few of the earlier mentioned methods.
You can do the following:
static async Task<string> WsGetResponseString(WebRequest webreq, CancellationToken cancelToken)`
{
cancelToken.Register(webreq.Abort);
using (var response = await webReq.GetResponseAsync())
using (var stream = response.GetResponseStream())
using (var destStream = new MemoryStream())
{
await stream.CopyToAsync(destStream, 4096, cancelToken);
return Encoding.UTF8.GetString(destStream.ToArray());
}
}

Your code only calls ThrowIfCancellationRequested() once after starting the GetDocumentAsync method, making the window for catching a cancel very small.
You need to pass the CancellationToken to GetDocumentById and have it either call ThrowIfCancellationRequested in between operations or perhaps pass the token straight to some calls at a lower level.
As a quick test of the plumbing between your calling method and the CancellationToken, you could change GetDocumentAsync to read:
cancelToken.ThrowIfCancellationRequested();
documentResult = await Task.Run(() => manager.GetDocumentById(documentId));
cancelToken.ThrowIfCancellationRequested();
And call CancelToken.CancelAfter(50) or simlar just after creating the CancellationTokenSource... You may need to adjust the value of 50 depending on how long GetDocumentById takes to run.
[Edit]
Given your edit to the question, the quickest fix is to pass the CancelToken down to WsGetResponseString and use CancelToken.Register() to call WebRequest.Abort().
You could also use CancelAfter() to implement your 50s timeout, switch from BeginGetResponse..EndGetResponse to GetResponseAsync etc.

Related

C # HttpWebRequest , Fire and forget an API call

In my WCF service, I have to make a call to an API, where I wanted to do a Fire and Forget implementation. And If possible just capture the errors if any.(That's fine too , if not an option)
I am planning to do the following implementation, what are the issues it could lead to? By doing the following implementation is going to leave a huge number of open connections. Or what could be the issue? Please help in understanding how in a better way this can be implemented.
void SendRequest(inputs)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(URL);
request.Method = "POST";
request.ContentType = "application/xml";
byte[] requestBytes = Encoding.UTF8.GetBytes(inputXML);
using (Stream requestStream = request.GetRequestStream())
{
requestStream.Write(requestBytes, 0, requestBytes.Length);
}
request.GetResponseAsync();
}
Main()
{
try
SendRequest(inputs);
catch ex
log ex;
}
First, make fully async version of your code
using System.Threading;
public async Task<System.Net.WebResponse> SendRequestAsync(
string inputXML, string url, CancellationToken cancellationToken)
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
request.Method = "POST";
request.ContentType = "application/xml";
byte[] requestBytes = Encoding.UTF8.GetBytes(inputXML);
// GetRequestStreamAsync is a lightweight operation I assume
// so we don't really need any cancellation
using (Stream requestStream = await request.GetRequestStreamAsync())
{
// Idk if we really need cancellation here, but just in case of big request we might need to
await requestStream.WriteAsync(
requestBytes, 0, requestBytes.Length, cancellationToken);
}
// Close any long-running request
using (cancellationToken.Register(() => request.Abort(), useSynchronizationContext: false))
{
var response = await request.GetResponseAsync();
cancellationToken.ThrowIfCancellationRequested();
return response;
}
}
Let's create an async void method, but make it safe. It will basically execute in "fire-and-forget" manner.
public async void DoWork(string inputXML, string url, CancellationToken ct)
{
try
{
using(var response = await SendRequestAsync(inputXML, url, ct))
{
var httpResponse = (HttpWebResponse) response;
// Use 201 Created or whatever you need
if (httpResponse.StatusCode != HttpStatusCode.Created)
{
// TODO: handle wrong status code
}
}
}
catch (Exception e)
{
if (ct.IsCancellationRequested)
{
Console.WriteLine("Cancelled");
}
else
{
// TODO: handle exception
}
}
}
private static CancellationTokenSource _cts = new CancellationTokenSource();
public static void Main (string[] args)
{
DoWork("xml string", "example.com", cts.Token);
Console.WriteLine("Boom!");
if (Console.ReadLine() == "exit")
{
// Cancel the damn job
cts.Cancel();
}
}
It's important to handle all errors from inside a DoWork, because following will not work
// Warning, will NOT catch an exception
public static void Main (string[] args)
{
try
{
DoWork("xml string", "example.com");
}
catch (Exception e)
{
}
}
EDIT: OP requested cancellation so I added cancellation
Please note that it's not best practice not to use fire and forget, especially if this a core layer of the application and you might miss important exceptions. When you use this technique you have to remember that the following happens:
Exception will be fail silently without any chance of catching them. normally you will want to log them or get a notification.
You have no idea when the code completes,
Since You don't need the code to complete and it might may not run to you would have no notification that it failed to complete.
A good case scenario for using this technique could be for updating a cache for an example.
Having said that, you could use the following techniques:
NET 4.5 allows us to use it via Task.Run
Task.Run(() => FireAndForget());
You could also start a thread with parameterless lambda:
(new Thread(() => {
FireAndForget();
}) {
Name = "Running Work Thread (FireAndForget)",
Priority = ThreadPriority.BelowNormal
}).Start();

Does CancellationToken removes file lock?

I have following async function:
private async Task<bool> ValidateFtpAsync()
{
return await Task.Run(
() =>
{
if(File.Exists("settings.xml"))
{
var xs = new XmlSerializer(typeof(Information));
using (var read = new FileStream("settings.xml", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
Information info = (Information)xs.Deserialize(read);
try
{
var DecryptedInfo = FileCryptoDecryptor.ReadEncryptedConfiguration("hakuna.xml.aes", Global_Variables.AppPassword);
string DecryptedFTPPass = EncryDecryptor.Decrypt(DecryptedInfo.FtpPassword, "UltraSecretPasswordNotGonnaSayItToYou");
return General_Functions.isValidConnection(info.HDSynologyIP, info.FtpUsername, DecryptedFTPPass);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
return false;
}
else
{
MessageBox.Show("Missing settings file.");
return false;
}
});
}
You can see it reads data from a file. My question is if i use CancellationToken for async method does it remove file lock when its in using block?
No, cancellation token by itself does not do anything (and in particular does not close files). The task's code needs to check state of the token repeatedly and act accordingly to perform cancellation.
It is unclear where do you plan to use a cancellation token in your case as there is no repeated operations... But since code has properly set with using(…){} statements irrespective where you break the operation the file will be correctly closed in finally block of using (var read = new FileStream(....

How to wait for HttpClient GetAsync call until it returns the request in C#

I am trying to get data by the HttpClient. Data vary in size, it could be from few bytes to megabyte. I noticed many times my application exist even before it returns from the GetAsync. How can I wait until GetAsync complete it call? From the main app:-
backup.DoSaveAsync();
Console.ForegroundColor = ConsoleColor.Yellow;
Console.BackgroundColor = ConsoleColor.Red;
// My app exist by printing this msg, wihout getting any data.
// someitmes it gets data and other times it gets notinng.
// I used sleep to wait to get the call completed.
Console.WriteLine("\nBackup has done successfully in SQL database")
public async void DoSaveAsync()
{
using (var client = GetHttpClient(BaseAddress, path, ApiKey))
{
Stream snapshot = await GetData(client, path);
if (snapshot != Stream.Null)
{
snapshot.Position = 0;
SaveSnapshot(snapshot);
}
}
}
private async Task<Stream> GetData(HttpClient client, string path)
{
HttpResponseMessage response = null;
try
{
response = await client.GetAsync(path);
System.Threading.Thread.Sleep(5000);
if (response.IsSuccessStatusCode == false)
{
Console.WriteLine($"Failed to get snapshot");
return Stream.Null;
}
return await response.Content.ReadAsStreamAsync();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return Stream.Null;
}
}
Code update after the comments and answer:
// in my main app, I have this code.
// How can I get the completed task or any error return by the task here.
backup.DoBackupAsync().Wait();
public async Task<Stream> DoSaveAsync()
{
using (var client = GetHttpClient(BaseAddress, SnapshotPath, ApiKey))
{
try
{
Stream snapshot = await GetSnapshot(client, SnapshotPath);
if (snapshot != Stream.Null)
{
snapshot.Position = 0;
SaveSnapshot(snapshot);
}
return snapshot;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return null;
}
}
}
As the method is async, the backup.DoSaveAsync() line only start a Task but doesn't wait for the result, so you may call Console.ReadLine (and possibly exit your program) before the task is completed. You should return Task instead of void - it's generally bad design to have a void async method, and younhave to await backup.DoSaveAsync() either via await (if you call from an async method), either via .Wait().
Also, in case of error in GetData, you don't return any error for DoSaveAsync - you may want to deal with this, in the current code, you would print "Failed to get snapshot" and then "Backup has done successfully in SQL database". Consider to not use Console.ReadLine in GetData and return a Task in DoSaveAsync indicating success
No need to put a thread.sleep here - you already await the result.

Can I cancel StreamReader.ReadLineAsync with a CancellationToken?

When I cancel my async method with the following content by calling the Cancel() method of my CancellationTokenSource, it will stop eventually. However since the line Console.WriteLine(await reader.ReadLineAsync()); takes quite a bit to complete, I tried to pass my CancellationToken to ReadLineAsync() as well (expecting it to return an empty string) in order to make the method more responsive to my Cancel() call. However I could not pass a CancellationToken to ReadLineAsync().
Can I cancel a call to Console.WriteLine() or Streamreader.ReadLineAsync() and if so, how do I do it?
Why is ReadLineAsync() not accepting a CancellationToken? I thought it was good practice to give async methods an optional CancellationToken parameter even if the method still completes after being canceled.
StreamReader reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{
if (ct.IsCancellationRequested){
ct.ThrowIfCancellationRequested();
break;
}
else
{
Console.WriteLine(await reader.ReadLineAsync());
}
}
Update:
Like stated in the comments below, the Console.WriteLine() call alone was already taking up several seconds due to a poorly formatted input string of 40.000 characters per line. Breaking this down solves my response-time issues, but I am still interested in any suggestions or workarounds on how to cancel this long-running statement if for some reason writing 40.000 characters into one line was intended (for example when dumping the whole string into a file).
.NET 6 brings Task.WaitAsync(CancellationToken). So one can write:
using StreamReader reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{
Console.WriteLine(await reader.ReadLineAsync().WaitAsync(cancellationToken).ConfigureAwait(false));
}
In .NET 7 (not yet released), it should be possible to write simply:
using StreamReader reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{
Console.WriteLine(await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false);
}
based on https://github.com/dotnet/runtime/issues/20824 and https://github.com/dotnet/runtime/pull/61898.
You can't cancel the operation unless it's cancellable. You can use the WithCancellation extension method to have your code flow behave as if it was cancelled, but the underlying would still run:
public static Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
return task.IsCompleted // fast-path optimization
? task
: task.ContinueWith(
completedTask => completedTask.GetAwaiter().GetResult(),
cancellationToken,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
}
Usage:
await task.WithCancellation(cancellationToken);
You can't cancel Console.WriteLine and you don't need to. It's instantaneous if you have a reasonable sized string.
About the guideline: If your implementation doesn't actually support cancellation you shouldn't be accepting a token since it sends a mixed message.
If you do have a huge string to write to the console you shouldn't use Console.WriteLine. You can write the string in a character at a time and have that method be cancellable:
public void DumpHugeString(string line, CancellationToken token)
{
foreach (var character in line)
{
token.ThrowIfCancellationRequested();
Console.Write(character);
}
Console.WriteLine();
}
An even better solution would be to write in batches instead of single characters. Here's an implementation using MoreLinq's Batch:
public void DumpHugeString(string line, CancellationToken token)
{
foreach (var characterBatch in line.Batch(100))
{
token.ThrowIfCancellationRequested();
Console.Write(characterBatch.ToArray());
}
Console.WriteLine();
}
So, in conclusion:
var reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{
DumpHugeString(await reader.ReadLineAsync().WithCancellation(token), token);
}
I generalized this answer to this:
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken, Action action, bool useSynchronizationContext = true)
{
using (cancellationToken.Register(action, useSynchronizationContext))
{
try
{
return await task;
}
catch (Exception ex)
{
if (cancellationToken.IsCancellationRequested)
{
// the Exception will be available as Exception.InnerException
throw new OperationCanceledException(ex.Message, ex, cancellationToken);
}
// cancellation hasn't been requested, rethrow the original Exception
throw;
}
}
}
Now you can use your cancellation token on any cancelable async method. For example WebRequest.GetResponseAsync:
var request = (HttpWebRequest)WebRequest.Create(url);
using (var response = await request.GetResponseAsync())
{
. . .
}
will become:
var request = (HttpWebRequest)WebRequest.Create(url);
using (WebResponse response = await request.GetResponseAsync().WithCancellation(CancellationToken.None, request.Abort, true))
{
. . .
}
See example http://pastebin.com/KauKE0rW
I like to use an infinite delay, the code is quite clean.
If waiting is complete WhenAny returns and the cancellationToken will throw. Else, the result of task will be returned.
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
using (var delayCTS = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
var waiting = Task.Delay(-1, delayCTS.Token);
var doing = task;
await Task.WhenAny(waiting, doing);
delayCTS.Cancel();
cancellationToken.ThrowIfCancellationRequested();
return await doing;
}
}
You can't cancel Streamreader.ReadLineAsync(). IMHO this is because reading a single line should be very quick. But you can easily prevent the Console.WriteLine() from happening by using a separate task variable.
The check for ct.IsCancellationRequested is also redundand as ct.ThrowIfCancellationRequested() will only throw if cancellation is requested.
StreamReader reader = new StreamReader(dataStream);
while (!reader.EndOfStream)
{
ct.ThrowIfCancellationRequested();
string line = await reader.ReadLineAsync());
ct.ThrowIfCancellationRequested();
Console.WriteLine(line);
}

How to attach CancellationTokenSource to DownloadStringTaskAsync method and cancel the async call?

I an creating a sample example to call link using WebClient using async and await method now I want to attach cancel async call functionality also. But I am not able to get CancellationTokenSource token and attach DownloadStringTaskAsync to this cancellation token. Following Is my code can anyone tell me how to accomplish this.
private async void DoWork()
{
this.Cursor = Cursors.WaitCursor;
Write("DoWork started.");
cts = new CancellationTokenSource();
WebClient wc = new WebClient();
string result = await wc.DownloadStringTaskAsync(new Uri("http://gyorgybalassy.wordpress.com"));
if (result.Length < 100000)
{
Write("The result is too small, download started from second URL.");
result = await wc.DownloadStringTaskAsync(new Uri("https://www.facebook.com/balassy"));
}
Write("Download completed. Downloaded bytes: " + result.Length.ToString());
Write("DoWork ended.");
this.Cursor = Cursors.Default;
}
private void btnCancel_Click(object sender, EventArgs e)
{
Write("Cancellation started.");
this.cts.Cancel();
Write("Cancellation ended.");
}
When my Cancel button calls cts.Cancel the DownloadStringTaskAsync call is not canceled. Why cancel button is not able to cancel the Async calls?
The async capabilities of WebClient predate .Net 4.5, so it supports the Task-based Asynchronous Pattern only partially. That includes having its own cancellation mechanism: the CancelAsync() method, which works even with the new -TaskAsync methods. To call this method when a CancellationToken is canceled, you can use its Register() method:
cts.Token.Register(wc.CancelAsync);
As an alternative, you could use the new HttpClient, as Stephen suggested, which fully supports TAP, including CancellationTokens.
Extension methods based on svick's answer:
public static async Task<string> DownloadStringTaskAsync(this WebClient webClient, string url, CancellationToken cancellationToken) {
using (cancellationToken.Register(webClient.CancelAsync)) {
return await webClient.DownloadStringTaskAsync(url);
}
}
public static async Task<string> DownloadStringTaskAsync(this WebClient webClient, Uri uri, CancellationToken cancellationToken) {
using (cancellationToken.Register(webClient.CancelAsync)) {
return await webClient.DownloadStringTaskAsync(uri);
}
}
WebClient doesn't support cancellation. I recommend you use a newer type such as HttpClient:
...
cts = new CancellationTokenSource();
string result;
using (var client = new HttpClient())
using (var response = await client.GetAsync("http://gyorgybalassy.wordpress.com", cts.Token))
{
result = await response.Content.ReadAsStringAsync();
}
if (result.Length < 100000)
...
The GetAsync method by default will not complete until it reads the entire response, so the await response.Content.ReadAsStringAsync line will actually complete synchronously.

Categories

Resources