TAP - 使用内部EAP方法来减少代码

时间:2015-11-22 20:48:30

标签: c# asynchronous async-await

我有一个看起来像这样的同步方法:

public void DownloadPackages()
    {
        long received = 0;
        double total = PackageConfigurations.Select(config => SizeHelper.GetRemoteFileSize(config.UpdatePackageUri))
            .Where(updatePackageSize => updatePackageSize != null)
            .Sum(updatePackageSize => updatePackageSize.Value);

        if (!Directory.Exists(_applicationUpdateDirectory))
            Directory.CreateDirectory(_applicationUpdateDirectory);

        foreach (var updateConfiguration in PackageConfigurations)
        {
            WebResponse webResponse = null;
            try
            {
                var webRequest = WebRequest.Create(updateConfiguration.UpdatePackageUri);
                using (webResponse = webRequest.GetResponse())
                {
                    var buffer = new byte[1024];
                    _packageFilePaths.Add(new UpdateVersion(updateConfiguration.LiteralVersion),
                        Path.Combine(_applicationUpdateDirectory,
                            $"{updateConfiguration.LiteralVersion}.zip"));
                    using (FileStream fileStream = File.Create(Path.Combine(_applicationUpdateDirectory,
                        $"{updateConfiguration.LiteralVersion}.zip")))
                    {
                        using (Stream input = webResponse.GetResponseStream())
                        {
                            if (input == null)
                                throw new Exception("The response stream couldn't be read.");

                            int size = input.Read(buffer, 0, buffer.Length);
                            while (size > 0)
                            {
                                if (_downloadCancellationTokenSource.IsCancellationRequested)
                                {
                                    fileStream.Flush();
                                    fileStream.Close();
                                    throw new OperationCanceledException(_downloadCancellationTokenSource.Token);
                                }

                                fileStream.Write(buffer, 0, buffer.Length);
                                received += size;
                                    OnUpdateDownloadProgressChanged(received,
                                        (long)total, (float)(received / total) * 100);
                                size = input.Read(buffer, 0, buffer.Length);
                            }

                        }
                    }
                }
            }
            finally
            {
                webResponse?.Close();
            }
        }
    }

好吧,我已经实施了EAP。这意味着我编写了一个方法,将此方法包装在一个任务中并引发事件:

public void DownloadPackagesAsync()
    {
        _downloadCancellationTokenSource.Dispose();
        _downloadCancellationTokenSource = new CancellationTokenSource();
               Task.Factory.StartNew(DownloadPackages).ContinueWith(DownloadTaskCompleted,
            _downloadCancellationTokenSource.Token,
            TaskContinuationOptions.None,
            TaskScheduler.Default);
    }

private void DownloadTaskCompleted(Task task)
    {
        if (task.IsCanceled)
            return;

        var exception = task.Exception;
        if (exception != null)
            OnUpdateDownloadFailed(exception.InnerException ?? exception);
        else
            OnUpdateDownloadFinished(this, EventArgs.Empty);
    }

第一个问题:更新同步方法的进度是否正确,还是有另一种方法?

第二个问题:有一个私有字段管理我班级中的CancellationTokenSource,我提供的方法如下:

public void CancelDownload()
    {
        _downloadCancellationTokenSource.Cancel();
    }

调用此方法时,将取消下载。但是,当我使用TAP时,这也是正确的吗?

现在,第三个问题涉及使用TAP的异步方法。 它应该是public async Task DownloadPackagesTaskAsync(IProgress<UpdateDownloadProgressChangedEventArgs> progress)。实际上我需要复制整个代码并进行调整,但这会给我带来很多冗余的线条,我想。我想过在内部使用EAP方法。问题是我没有任何返回类型,并且我不能使用TaskCompletionSource来使用它。我的方向是.NET-Framework中的代码:http://referencesource.microsoft.com/#System/net/System/Net/webclient.cs,d250a06fb9c3ac77,references

有没有办法实现这个目标?

1 个答案:

答案 0 :(得分:3)

  

好吧,我已经实施了EAP

为什么呢?特别是因为你显然打算实施TAP,为什么还要费心去做?就此而言,为什么DownloadPackages()是同步的? Web API非常好地异步工作,以这种方式使用它们可以更有效地利用您的线程资源,当然,它自然适合您自己代码的基于TAP的API。

就我个人而言,如果您愿意,我只需将DownloadPackages()更改为async Task DownloadPackagesAsync()(或包含IProgress<T>参数),在方法中进行适当的更改以调用网络和文件API与await异步,并将其保留在该位置。

那说......

  

更新同步方法的进度是否正确,还是有其他方法?

“正确”按照什么标准?

当然它会像你拥有它一样工作。你遗漏了OnUpdateDownloadProgressChanged()的实现,所以我们不可能具体知道它的作用。但是,EAP代码的客户端需要根据需要自己处理跨线程调用并不罕见,所以我在这里看不出任何错误。

当然,请注意,如果您将main方法实现为async,则会产生编组回到进度更新的正确上下文的效果。哪个更好。但不是必需的。

  

我提供的方法看起来像这样......   调用此方法时,将取消下载。但是,当我使用TAP时,这也是正确的吗?

再次,根据什么标准“正确”?我个人的感觉就是这样。我不想公开TaskCancellationSource本身,所以在公共方法中封装cancel操作对我来说似乎是正确的。但是,如果不知道你想要判断设计的标准,就很难肯定地说。

  

现在,第三个问题涉及使用TAP的异步方法。它应该是public async Task DownloadPackagesTaskAsync(IProgress<UpdateDownloadProgressChangedEventArgs> progress)。实际上我需要复制整个代码并进行调整,但这会给我带来很多冗余的线条,我想。我想过在内部使用EAP方法。问题是我没有任何返回类型,并且我不能使用TaskCompletionSource来使用它。 ......有什么方法可以实现这个目标吗?

同样,如果您只是将原始API实现为async,则甚至不会出现这种情况。那就是说,我不明白你的意见。如果您想获取现有的代码并将其作为TAP风格的API呈现,在我看来,您可以简单地执行此操作:

public Task DownloadPackagesAsync()
{
    _downloadCancellationTokenSource.Dispose();
    _downloadCancellationTokenSource = new CancellationTokenSource();

    return Task.Factory.StartNew(DownloadPackages).ContinueWith(DownloadTaskCompleted,
        _downloadCancellationTokenSource.Token,
        TaskContinuationOptions.None,
        TaskScheduler.Default);
}

添加IProgress<T>是微不足道的。如果您真的想保留EAP和TAP样式API,则只需根据您的喜好调整其中一个。例如,如果您希望底层实现使用EAP并将其包装在TAP中,您可以自己订阅相应的事件并在处理程序中调用IProgress<T>.Report()。或者,如果您希望底层实现使用TAP,您可以在EAP包装器中将IProgress<T>实例传递给实现,并在IProgress<T>的回调中引发相应的事件。

我没有看到返回类型的缺乏与任何事情有什么关系。没有人强迫你没有返回类型,是吗?我也不认为有必要使用TaskCompletionSource;您已经有多个选项可以返回将在适当的时间完成的Task对象。