Search code examples
c#win-universal-app.net-4.5filestreamwindows-10-universal

WriteToStreamAsync cancel does not work


I am running a Task, which copies from one stream to another. This works without problems, including progress reporting. But i cant cancel the task. If i fire the CancellationToken, the copy progress runs till its completion, then the task is cancelled, but this is of course to late. Here is my actual code

private async Task Download(Uri uriToWork, CancellationToken cts)
{
    HttpClient httpClient = new HttpClient();
    HttpRequestMessage requestAction = new HttpRequestMessage();
    requestAction.Method = new HttpMethod("GET");
    requestAction.RequestUri = uriToWork;

    HttpResponseMessage httpResponseContent = await httpClient.SendRequestAsync(requestAction, HttpCompletionOption.ResponseHeadersRead);

    using (Stream streamToRead = (await httpResponseContent.Content.ReadAsInputStreamAsync()).AsStreamForRead())
    {
        string fileToWrite = Path.GetTempFileName();
        using (Stream streamToWrite = File.Open(fileToWrite, FileMode.Create))
        {
            await httpResponseContent.Content.WriteToStreamAsync(streamToWrite.AsOutputStream()).AsTask(cts, progressDownload); 

            await streamToWrite.FlushAsync();
            //streamToWrite.Dispose();
         }

         await streamToRead.FlushAsync();
         //streamToRead.Dispose();
    }
    httpClient.Dispose();
}

Can someone help me please, or can explain, why it does not work?


Solution

  • Is it this operation that continues until it completes ?

    await httpResponseContent.Content.WriteToStreamAsync(streamToWrite.AsOutputStream()).AsTask(cts, progressDownload); 
    

    Or is it this one ?

    await streamToWrite.FlushAsync();
    

    I think the latter needs probably to have the CancellationToken as well:

    await streamToWrite.FlushAsync(cts);
    

    Unfortunately I cannot answer why this cancel does not occur. However, a solution that consists in writing the Stream in chunks may help.


    Here is something very dirty that works:

    private async Task Download(Uri uriToWork, CancellationToken cts) {
    
        using(HttpClient httpClient = new HttpClient()) {
    
            HttpRequestMessage requestAction = new HttpRequestMessage();
            requestAction.Method = new HttpMethod("GET");
            requestAction.RequestUri = uriToWork;
    
            HttpResponseMessage httpResponseContent = await httpClient.SendRequestAsync(requestAction, HttpCompletionOption.ResponseHeadersRead);
            string fileToWrite = Path.GetTempFileName();
            using(Stream streamToWrite = File.Open(fileToWrite, FileMode.Create)) {
    
                // Disposes streamToWrite to force any write operation to fail
                cts.Register(() => streamToWrite.Dispose());
    
                try {
                    await httpResponseContent.Content.WriteToStreamAsync(streamToWrite.AsOutputStream()).AsTask(cts, p);
                }
                catch(TaskCanceledException) {
                    return; // "gracefully" exit when the token is cancelled
                }
    
                await streamToWrite.FlushAsync();
            }
        }
    }
    
    • I enclosed the httpClient in a using so a return disposes it properly.
    • I removed the streamToRead which was not used at all
    • Now here is the horror: I added a delegate that executes when the token is cancelled: it disposes streamToWrite while it is written to (ughhhh), which triggers an TaskCancelledException when WriteToStreamAsync cannot longer write in this disposed stream.

    Please dont throw a puke bag at me yet, I am not experienced enough in this "Universal" Framework which looks very different as the usual one.

    Here is a chunked stream solution that looks more acceptable. I shortened a bit the original code and added the IProgress as a parameter.


    async Task Download(Uri uriToWork, CancellationToken cts, IProgress<int> progress) {
        using(HttpClient httpClient = new HttpClient()) {
    
            var chunkSize = 1024;
            var buffer = new byte[chunkSize];
            int count = 0;
            string fileToWrite = Path.GetTempFileName();
    
            using(var inputStream = await httpClient.GetInputStreamAsync(uriToWork)) {
                using(var streamToRead = inputStream.AsStreamForRead()) {
                    using(Stream streamToWrite = File.OpenWrite(fileToWrite)) {
                        int size;
                        while((size = await streamToRead.ReadAsync(buffer, 0, chunkSize, cts).ConfigureAwait(false)) > 0) {
                            count += size;
                            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => progress.Report(count));
                            // progress.Report(count);
                            await streamToWrite.WriteAsync(buffer, 0, size, cts).ConfigureAwait(false);
                        }
                    }
                }
            }
        }
    }