我有一个看起来像这样的同步方法:
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
有没有办法实现这个目标?
答案 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
对象。