使用新并行编程库的示例中的示例下载文件时如何添加正确的取消

时间:2009-12-11 16:12:37

标签: .net parallel-processing

我已经下载了并行编程团队的最新示例,但我没有成功添加取消下载文件的可能性。

以下是我结束的代码:

var wreq = (HttpWebRequest)WebRequest.Create(uri);

// Fire start event
DownloadStarted(this, new DownloadStartedEventArgs(remoteFilePath));

long totalBytes = 0;

wreq.DownloadDataInFileAsync(tmpLocalFile,
                             cancellationTokenSource.Token,
                             allowResume,
                             totalBytesAction =>
                             {
                                 totalBytes = totalBytesAction;
                             },
                             readBytes =>
                             {
                                 Log.Debug("Progression : {0} / {1} => {2}%", readBytes, totalBytes, 100 * (double)readBytes / totalBytes);
                                 DownloadProgress(this, new DownloadProgressEventArgs(remoteFilePath, readBytes, totalBytes, (int)(100 * readBytes / totalBytes)));
                             })
    .ContinueWith( (antecedent ) =>
                      {
                          if (antecedent.IsFaulted)
                              Log.Debug(antecedent.Exception.Message);

                          //Fire end event
                          SetEndDownload(antecedent.IsCanceled, antecedent.Exception, tmpLocalFile, 0);
                      }, cancellationTokenSource.Token);

我想在下载完成后触发结束事件,因此是ContinueWith。

我稍微更改了示例代码以添加CancellationToken和2个代表来获取要下载的文件的大小,以及下载的进度:

return webRequest.GetResponseAsync()
    .ContinueWith(response =>
                      {
                          if (totalBytesAction != null)
                              totalBytesAction(response.Result.ContentLength);

                          response.Result.GetResponseStream().WriteAllBytesAsync(filePath, ct, resumeDownload, progressAction).Wait(ct);
                      }, ct);

我不得不将调用添加到Wait函数中,因为如果我不这样做,则该方法退出并且结束事件过早触发。

以下是修改后的方法扩展(很多代码,道歉:p)

public static Task WriteAllBytesAsync(this Stream stream, string filePath, CancellationToken ct, bool resumeDownload = false, Action<long> progressAction = null)
{
    if (stream == null) throw new ArgumentNullException("stream");

    // Copy from the source stream to the memory stream and return the copied data
    return stream.CopyStreamToFileAsync(filePath, ct, resumeDownload, progressAction);
}

public static Task CopyStreamToFileAsync(this Stream source, string destinationPath, CancellationToken ct, bool resumeDownload = false, Action<long> progressAction = null)
{
    if (source == null) throw new ArgumentNullException("source");
    if (destinationPath == null) throw new ArgumentNullException("destinationPath");

    // Open the output file for writing
    var destinationStream = FileAsync.OpenWrite(destinationPath);

    // Copy the source to the destination stream, then close the output file.
    return CopyStreamToStreamAsync(source, destinationStream, ct, progressAction).ContinueWith(t =>
    {
        var e = t.Exception;
        destinationStream.Close();

        if (e != null)
            throw e;
    }, ct, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Current);
}

public static Task CopyStreamToStreamAsync(this Stream source, Stream destination, CancellationToken ct, Action<long> progressAction = null)
{
    if (source == null) throw new ArgumentNullException("source");
    if (destination == null) throw new ArgumentNullException("destination");

    return Task.Factory.Iterate(CopyStreamIterator(source, destination, ct, progressAction));
}

private static IEnumerable<Task> CopyStreamIterator(Stream input, Stream output, CancellationToken ct, Action<long> progressAction = null)
{
    // Create two buffers.  One will be used for the current read operation and one for the current
    // write operation.  We'll continually swap back and forth between them.
    byte[][] buffers = new byte[2][] { new byte[BUFFER_SIZE], new byte[BUFFER_SIZE] };
    int filledBufferNum = 0;
    Task writeTask = null;
    int readBytes = 0;

    // Until there's no more data to be read or cancellation
    while (true)
    {
        ct.ThrowIfCancellationRequested();

        // Read from the input asynchronously
        var readTask = input.ReadAsync(buffers[filledBufferNum], 0, buffers[filledBufferNum].Length);

        // If we have no pending write operations, just yield until the read operation has
        // completed.  If we have both a pending read and a pending write, yield until both the read
        // and the write have completed.
        yield return writeTask == null
                         ? readTask
                         : Task.Factory.ContinueWhenAll(new[]
                                                            {
                                                                readTask,
                                                                writeTask
                                                            },
                                                        tasks => tasks.PropagateExceptions());

        // If no data was read, nothing more to do.
        if (readTask.Result <= 0)
            break;

        readBytes += readTask.Result;

        if (progressAction != null) 
            progressAction(readBytes);

        // Otherwise, write the written data out to the file
        writeTask = output.WriteAsync(buffers[filledBufferNum], 0, readTask.Result);

        // Swap buffers
        filledBufferNum ^= 1;
    }
}

基本上,在被调用方法链的末尾,如果已请求取消,我让CancellationToken抛出OperationCanceledException。

我希望在吸引人的代码中获取IsFaulted == true并使用已取消的标志和正确的异常触发结束事件。

但我得到的是行上未处理的异常

response.Result.GetResponseStream()。WriteAllBytesAsync(filePath,ct,resumeDownload,progressAction).Wait(ct);

告诉我我没有捕获到AggregateException。我尝试了各种各样的东西,但是我没有成功地使整个过程正常工作。

你们有没有人玩过那个图书馆并且可以帮助我?

提前致谢

麦克

1 个答案:

答案 0 :(得分:0)

这几乎是我所期望的行为。我无法完全绕过你的代码,但我怀疑如果你看一下AggregateException.InnerExceptions属性,你会在那里找到一个TaskCancelled或OperationCancelledException。取消令牌会导致取消的任务抛出异常,该任务通常由调用代码捕获,因为它等待任务完成,并且它被包装在聚合中。

以下链接有更多详细信息。

http://msdn.microsoft.com/en-us/library/dd997396.aspx

http://msdn.microsoft.com/en-us/library/dd997364.aspx

我不会写更多,因为我不是100%确信我理解你的代码。