使用CancellationToken的Async / await不取消操作

时间:2015-05-05 13:02:36

标签: c# async-await cancellationtokensource

我想使用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;
}

2 个答案:

答案 0 :(得分:5)

  

我想使用CancellationToken中止文件下载

下载文件是一种I / O操作,在.NET平台上可以使用异步可取消(基于I / O完成端口)功能。但你似乎没有使用它们。

相反,您似乎正在使用执行阻止I / O的Task.Run创建(一系列)任务,其中取消令牌不会传递到Task.Run链中的每个任务。

有关执行异步,等待和可取消文件下载的示例,请参阅:

您可以执行以下操作:

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());
    }
}

答案 1 :(得分:0)

您的代码仅在启动GetDocumentAsync方法后调用ThrowIfCancellationRequested()一次,使得取消的窗口非常小。

你需要将CancellationToken传递给GetDocumentById并让它在两次操作之间调用ThrowIfCancellationRequested,或者直接将令牌传递给较低级别​​的某些调用。

作为对您的调用方法与CancellationToken之间管道的快速测试,您可以将GetDocumentAsync更改为:

cancelToken.ThrowIfCancellationRequested();
documentResult = await Task.Run(() => manager.GetDocumentById(documentId));
cancelToken.ThrowIfCancellationRequested();

在创建CancelToken.CancelAfter(50)之后立即拨打CancellationTokenSource或者simlar ...您可能需要根据GetDocumentById运行的时间长度调整50的值。

[编辑]

根据您对该问题的修改,最快的解决方法是将CancelToken向下传递到WsGetResponseString并使用CancelToken.Register()致电WebRequest.Abort()

您还可以使用CancelAfter()来实现50秒超时,从BeginGetResponse..EndGetResponse切换到GetResponseAsync等。