簡體   English   中英

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

我想使用CancellationToken中止文件下載

下載文件是一項I / O操作,.NET平台上提供了異步可取消(基於I / O完成端口)功能。 但是您似乎並沒有使用它們。

相反,您似乎正在使用執行阻塞I / O的Task.Run創建(一系列)任務,其中取消令牌沒有傳遞到Task.Run鏈中的每個任務。

有關執行異步,等待和可取消文件下載的示例,請參考:

  • 使用HttpClient如何異步復制和取消HttpContent?
  • Windows Phone在Windows Phone 8中下載並保存文件異步
  • 使用WebClient :有其自己的取消機制: CancelAsync方法,您可以使用令牌的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());
    }
}

您的代碼在啟動GetDocumentAsync方法后僅調用ThrowIfCancellationRequested()一次,這使得捕獲取消的窗口非常小。

您需要將CancellationToken傳遞給GetDocumentById,並ThrowIfCancellationRequested在操作之間調用ThrowIfCancellationRequested ,或者將令牌直接傳遞給較低級別​​的某些調用。

為了快速測試調用方法和CancellationToken之間的關系,您可以將GetDocumentAsync更改為:

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

並在創建CancellationTokenSource之后立即調用CancelToken.CancelAfter(50)或類似方法...您可能需要根據GetDocumentById運行多長時間來調整值50。

[編輯]

根據您對問題的編輯,最快的解決方法是將CancelToken向下傳遞給WsGetResponseString並使用CancelToken.Register()調用WebRequest.Abort()

您還可以使用CancelAfter()來實現您的50秒超時,從BeginGetResponse..EndGetResponse切換到GetResponseAsync等。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM