Async/await 使用 CancellationToken 不会取消操作
Async/await with CancellationToken doesn't cancel the operation
我想使用 CancellationToken
中止文件下载。这是我试过的:
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;
}
然后应该使用 cancelToken 来取消操作:
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 ();
}
}
好像IsCancellationRequested
没有更新了。所以操作不会被取消。我也试过用这个
cancelToken.ThrowIfCancellationRequested();
try{
documentResult = await Task.Run(() => manager.GetDocumentById (documentId), cancelToken);
} catch(TaskCanceledException){
Console.WriteLine("task canceled here");
}
但没有任何改变。
我做错了什么?
编辑:
以下是缺少的部分,如 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;
}
您的代码在启动 GetDocumentAsync 方法后仅调用 ThrowIfCancellationRequested()
一次,使得捕获取消的 window 非常小。
您需要将 CancellationToken
传递给 GetDocumentById 并让它在操作之间调用 ThrowIfCancellationRequested
或者可能将令牌直接传递给较低级别的某些调用。
作为对调用方法与 CancellationToken
之间管道的快速测试,您可以将 GetDocumentAsync
更改为:
cancelToken.ThrowIfCancellationRequested();
documentResult = await Task.Run(() => manager.GetDocumentById(documentId));
cancelToken.ThrowIfCancellationRequested();
并在创建 CancellationTokenSource
之后调用 CancelToken.CancelAfter(50)
或类似的...您可能需要根据 GetDocumentById
到 [=41= 的时间调整 50 的值].
[编辑]
鉴于您对问题的编辑,最快的解决方法是将 CancelToken
传递给 WsGetResponseString
并使用 CancelToken.Register()
调用 WebRequest.Abort()
.
您也可以使用 CancelAfter()
实现 50 秒超时,从 BeginGetResponse..EndGetResponse
切换到 GetResponseAsync
等
I want to use the CancellationToken to abort a file download
下载文件是一项 I/O 操作,在 .NET 平台上可以使用异步可取消(I/O 基于完成端口)函数。但是您似乎没有使用它们。
相反,您似乎正在使用 Task.Run
创建(一系列)任务,这些任务会执行阻塞 I/O,其中取消令牌不会传递给 Task.Run
中的每个任务链。
异步、可等待和可取消文件下载的例子,参考:
- 使用
HttpClient
:
- WindowsPhone:
Downloading and saving a file Async in Windows Phone 8
- 使用
WebClient
:有自己的取消机制:CancelAsync method, you can connect it to your cancellation token, using the token's Register方法:
myToken.Register(myWebclient.CancelAsync);
- 使用抽象 WebRequest:如果它不是使用附加的取消令牌创建的,就像您编辑的示例的情况一样,并且您实际上并没有下载文件, 但读取一个内容字符串,你需要结合使用前面提到的几种方法。
您可以执行以下操作:
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());
}
}
我想使用 CancellationToken
中止文件下载。这是我试过的:
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;
}
然后应该使用 cancelToken 来取消操作:
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 ();
}
}
好像IsCancellationRequested
没有更新了。所以操作不会被取消。我也试过用这个
cancelToken.ThrowIfCancellationRequested();
try{
documentResult = await Task.Run(() => manager.GetDocumentById (documentId), cancelToken);
} catch(TaskCanceledException){
Console.WriteLine("task canceled here");
}
但没有任何改变。
我做错了什么?
编辑:
以下是缺少的部分,如 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;
}
您的代码在启动 GetDocumentAsync 方法后仅调用 ThrowIfCancellationRequested()
一次,使得捕获取消的 window 非常小。
您需要将 CancellationToken
传递给 GetDocumentById 并让它在操作之间调用 ThrowIfCancellationRequested
或者可能将令牌直接传递给较低级别的某些调用。
作为对调用方法与 CancellationToken
之间管道的快速测试,您可以将 GetDocumentAsync
更改为:
cancelToken.ThrowIfCancellationRequested();
documentResult = await Task.Run(() => manager.GetDocumentById(documentId));
cancelToken.ThrowIfCancellationRequested();
并在创建 CancellationTokenSource
之后调用 CancelToken.CancelAfter(50)
或类似的...您可能需要根据 GetDocumentById
到 [=41= 的时间调整 50 的值].
[编辑]
鉴于您对问题的编辑,最快的解决方法是将 CancelToken
传递给 WsGetResponseString
并使用 CancelToken.Register()
调用 WebRequest.Abort()
.
您也可以使用 CancelAfter()
实现 50 秒超时,从 BeginGetResponse..EndGetResponse
切换到 GetResponseAsync
等
I want to use the CancellationToken to abort a file download
下载文件是一项 I/O 操作,在 .NET 平台上可以使用异步可取消(I/O 基于完成端口)函数。但是您似乎没有使用它们。
相反,您似乎正在使用 Task.Run
创建(一系列)任务,这些任务会执行阻塞 I/O,其中取消令牌不会传递给 Task.Run
中的每个任务链。
异步、可等待和可取消文件下载的例子,参考:
- 使用
HttpClient
: - WindowsPhone: Downloading and saving a file Async in Windows Phone 8
- 使用
WebClient
:有自己的取消机制:CancelAsync method, you can connect it to your cancellation token, using the token's Register方法:myToken.Register(myWebclient.CancelAsync);
- 使用抽象 WebRequest:如果它不是使用附加的取消令牌创建的,就像您编辑的示例的情况一样,并且您实际上并没有下载文件, 但读取一个内容字符串,你需要结合使用前面提到的几种方法。
您可以执行以下操作:
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());
}
}