2015-05-05 10 views
1

Я хочу использовать CancellationToken, чтобы прервать загрузку файла. Это то, что я пробовал:Async/wait with 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"); 
} 

но ничего не изменилось.

Что я делаю неправильно?

Edit:

Вот недостающие части, как 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; 
} 
+6

Я не очень понимаю, что вы пытаетесь сделать. Вы вызываете ThrowIfCancellationRequested * перед тем, как вы начинаете выполнять работу, которая вряд ли что-либо сделает. Вам необходимо периодически вызывать его внутри кода, который заставляет работу работать. Например, внутри цикла. – svinja

+0

Я не мог воспроизвести проблему при использовании 'Task.Delay' вместо' webservice.GetDocumentAsync'. Так что, возможно, этот служебный вызов или следующий код ('do some other stuff') не полностью поддерживают совместную отмену задачи. – Dirk

+0

Что делает 'GetDocumentById'? Если вы отмените токен после запуска GetDocumentById, никто не заметит, так как он не контролируется. –

ответ

5

Я хочу использовать CancellationToken, чтобы прервать загрузку файла

Загрузка файла является Операция ввода-вывода, для которой на платформе .NET доступны асинхронные функции отмены (на основе ввода-вывода). Но вы, кажется, не используете их.

Вместо этого вы создаете (цепочку) задач, используя Task.Run, которые выполняют блокировку ввода-вывода, где токен отмены не передается каждой задаче в вашей цепочке Task.Run.

Примеры делать асинхр, awaitable и аннулированию загрузки файлов, см:

  • Использование HttpClient: How to copy HttpContent async and cancelable?
  • Windows Phone: Downloading and saving a file Async in 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()); 
    } 
} 
+0

Я уже думал об использовании 'WebClient', но реализации, которые я видел, используют URI. В моем случае я создаю отдельный веб-запрос (см. Отредактированный вопрос). Так как это можно сделать здесь? – testing

+0

@testing Я обновил ответ, включив вариант «WebRequest» – Alex

+0

Большое спасибо. Я попробую.Если я хочу прочитать строку и хочу сохранить ее как файл без буферизации в памяти, могу ли я просто использовать 'File.Write' вместо' MemoryStream', как в [одном] (http://stackoverflow.com/a/21573527/426227) ваших связанных примеров? Я думаю, что это должно сработать, и для этого «CopyToAsync» и конвертация UTF-8 больше не нужны. – testing

0

Ваш код вызывает только ThrowIfCancellationRequested() сразу после запуска метода GetDocumentAsync, что делает окно для ловли отменить очень мало.

Вам необходимо передать CancellationToken в GetDocumentById и позвонить либо по телефону ThrowIfCancellationRequested между операциями, либо, возможно, передать токен прямо на некоторые вызовы на более низком уровне.

В быстрой проверки водопроводом между вашим методом вызова и CancellationToken, вы можете изменить GetDocumentAsync следующим образом:

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

Позвони CancelToken.CancelAfter(50) или simlar только после создания CancellationTokenSource ... Вы, возможно, потребуется отрегулируйте значение 50 в зависимости от того, как долго выполняется GetDocumentById.

[Редактировать]

Учитывая вашу правку на вопрос, самый быстрый починка пройти CancelToken вниз WsGetResponseString и использовать CancelToken.Register() для вызова WebRequest.Abort().

Вы также можете использовать CancelAfter() реализовать свой 50s тайм-аут, переход от BeginGetResponse..EndGetResponse к GetResponseAsync и т.д.

+0

Я получаю * Операция была отменена. * На выходе консоли, но кажется, что она вызывается после завершения 'ожидания '. Исключение выбрано в * GetDocumentAsync *. – testing

+0

Да, это будет выброшено на третьей строке выше. Вероятно, вы не хотите улавливать 'OperationCancelledException' в этом методе и позволять ему переходить в' retrieveDocument', который должен обрабатывать его и печатать на консоль. –