WriteToStreamAsync 取消不起作用

WriteToStreamAsync cancel does not work

我是运行一个任务,它从一个流复制到另一个流。这没有问题,包括进度报告。但是我不能取消任务。如果我触发 CancellationToken,复制进度将一直运行到完成,然后任务将被取消,但这当然已经晚了。这是我的实际代码

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

有人可以帮助我,或者可以解释为什么它不起作用吗?

这个操作是否一直持续到完成?

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

还是这一个?

await streamToWrite.FlushAsync();

我认为后者可能也需要有 CancellationToken:

await streamToWrite.FlushAsync(cts);

不幸的是,我无法回答为什么没有发生这种取消。但是,将 Stream 写入块 的解决方案可能会有所帮助。


这里有一些非常脏的东西:

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();
        }
    }
}
  • 我将 httpClient 包含在 using 中,因此 return 可以正确处理它。
  • 我删除了根本不用的streamToRead
  • 现在可怕的是:我添加了一个在令牌被取消时执行的委托:它在写入 (ughhhh) 时处理 streamToWrite,当 WriteToStreamAsync 不能时触发 TaskCancelledException不再在此处理的流中写入。

请不要向我扔呕吐袋,我对这个 "Universal" 框架没有足够的经验,它看起来与通常的框架非常不同。

这里有一个chunked stream的方案,看起来更容易接受。我稍微缩短了原始代码并添加了 IProgress 作为参数。


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

阻塞操作很可能不是WriteToStreamAsync()而是FlushAsync(),所以@Larry的假设应该是正确的,FlushAsync方法也需要取消令牌。