Отмена IInputStream.ReadAsync, если соединение с хостом потеряно
Я пишу решение UWP (WinRT), которое загружает файл с сервера и сохраняет на диск, показывая прогресс. Для этого я расширил метод IAsyncOperationWithProgress.
Моя проблема в том, что в одной строке:
while ((await responseStream.ReadAsync(streamReadBuffer, bufferLength, InputStreamOptions.None)).Length > 0 && !token.IsCancellationRequested)
Я жду, пока контент будет прочитан из потока. Если соединение потеряно, эта линия будет ждать бесконечно, пока соединение не будет восстановлено.
Как для UWP либо назначить тайм-аут для запроса Http, либо, в качестве альтернативы, отменить одну задачу?
Мой расширенный IAsyncOperationWithProgress:
private static IAsyncOperationWithProgress<HttpDownloadStatus, DownloadResponse> DownloadAsyncWithProgress(this HttpClient client, HttpRequestMessage request, CancellationToken cancelToken, StorageFile fileToStore)
{
const uint bufferLength = 2048;
var progressResponse = new DownloadResponse
{
File = fileToStore,
DownloadStatus = HttpDownloadStatus.Started,
BytesRecieved = 0,
Progress = 0.00
};
string result = string.Empty;
HttpDownloadStatus returnStatus = HttpDownloadStatus.Busy;
IBuffer streamReadBuffer = new Windows.Storage.Streams.Buffer(bufferLength);
var operation = client.SendRequestAsync(request, HttpCompletionOption.ResponseHeadersRead);
return AsyncInfo.Run<HttpDownloadStatus, DownloadResponse>((token, progress) =>
Task.Run(async () =>
{
try
{
if (cancelToken != CancellationToken.None) token = cancelToken;
HttpResponseMessage respMessage;
try
{
respMessage = await operation;
}
catch (Exception ex)
{
throw new Exception("Error sending download request - " + ex.Message);
}
progressResponse.TotalBytes = Convert.ToInt64(respMessage.Content.Headers.ContentLength);
using (var responseStream = await respMessage.Content.ReadAsInputStreamAsync())
{
using (var fileWriteStream = await fileToStore.OpenAsync(FileAccessMode.ReadWrite))
{
token.ThrowIfCancellationRequested();
while ((await responseStream.ReadAsync(streamReadBuffer, bufferLength, InputStreamOptions.None)).Length > 0 && !token.IsCancellationRequested)
{
while(DownloadManager.ShouldPauseDownload && DownloadManager.CurrentDownloadingBook.FileName == fileToStore.Name)
{
if (token.IsCancellationRequested)
break;
}
progressResponse.DownloadStatus = HttpDownloadStatus.Busy;
if (token.IsCancellationRequested)
{
// progressResponse.DownloadStatus = HttpDownloadStatus.Cancelled;
// returnStatus = HttpDownloadStatus.Cancelled;
break;
}
;
await fileWriteStream.WriteAsync(streamReadBuffer);
progressResponse.BytesRecieved += (int)streamReadBuffer.Length;
progressResponse.Progress = (progressResponse.BytesRecieved / (double)progressResponse.TotalBytes) * 100;
//Only give response when close to a byte
if (progressResponse.BytesRecieved % 1048576 == 0)
{
Debug.WriteLine("Should be 1 meg: " + progressResponse.BytesRecieved);
progress.Report(progressResponse);
}
} //while (offset < contentLength);
if (token.IsCancellationRequested)
{
progressResponse.DownloadStatus = HttpDownloadStatus.Cancelled;
returnStatus = HttpDownloadStatus.Cancelled;
}
}
}
if(returnStatus == HttpDownloadStatus.Busy) //only set it if it was still legitimately busy
returnStatus = HttpDownloadStatus.Complete;
return returnStatus;
}
catch (TaskCanceledException tce)
{
Debug.WriteLine("CANCEL - Download cancellation token caught from within task");
return HttpDownloadStatus.Cancelled;
}
}, token));
}
Как называется приведенный выше код:
HttpClient httpClient = new HttpClient(PFilter);
try
{
DownloadResponse downloadResponse = new DownloadResponse { File = fileToSave, DownloadStatus = HttpDownloadStatus.Busy };
CancellationToken cancelToken = m_CancellationSource.Token;
HttpRequestMessage requestMsg = new HttpRequestMessage(HttpMethod.Get, downloadUri);
IAsyncOperationWithProgress<HttpDownloadStatus,DownloadResponse> operationWithProgress = httpClient.DownloadAsyncWithProgress(requestMsg, cancelToken, fileToSave);
operationWithProgress.Progress = new AsyncOperationProgressHandler<HttpDownloadStatus, DownloadResponse>((result, progress) => { progressDelegate(progress); });
var response = await operationWithProgress;
downloadResponse.DownloadStatus = response;
if (response == HttpDownloadStatus.Complete)
{
return HttpWebExceptionResult.Success;
}
else if (response == HttpDownloadStatus.ConnectionLost)
return HttpWebExceptionResult.ConnectionFailure;
else if (response == HttpDownloadStatus.Cancelled)
return HttpWebExceptionResult.RequestCanceled;
else
return HttpWebExceptionResult.UnexpectedError;
}
catch (TaskCanceledException tce)
{
Debug.WriteLine("CANCEL - token caught from StartDownloadAsync ");
return HttpWebExceptionResult.RequestCanceled;
}
catch (Exception ex)
{
return HttpWebExceptionResult.UnexpectedError;
}
1 ответ
Если вы хотите отменить IAsyncOperation
операция из WinRT API, вам нужно будет преобразовать его в Task
сначала, а затем обеспечивает CancellationToken
срок действия истекает по истечении времени ожидания.
В этом примере inputStream.ReadAsync()
будет отменен, если он не завершился в течение 2 секунд:
var timeoutCancellationSource = new CancellationTokenSource(TimeSpan.FromSeconds(2));
var response = await inputStream.ReadAsync(buffer, bufferLength, InputStreamOptions.None).AsTask(timeoutCancellationSource.Token);
Если вы не хотите ждать 2 секунды, вы можете позвонить Cancel()
на CancellationTokenSource
в любое время вы хотите.
timeoutCancellationSource.Cancel();