Reputation: 20289
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;
}
Upvotes: 1
Views: 5203
Reputation: 12320
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.
Upvotes: 0
Reputation: 13234
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:
HttpClient
: How to copy HttpContent async and cancelable?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);
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());
}
}
Upvotes: 6