我们使用以下语句调用以下代码。
await this.asyncDownloadManager.DownloadFiles(this.applicationShellViewModel.StartupAudioFiles, this.applicationShellViewModel.SecurityCookie, securityCookieDomain).ConfigureAwait(false);
然后我们使用事件将下载的文件添加到ViewModel上的observablecollection(.net 4.5中的新线程安全版本)中。
public class AsyncDownloadManager
{
public event EventHandler<DownloadedEventArgs> FileDownloaded;
public async Task DownloadFiles(string[] fileIds, string securityCookieString, string securityCookieDomain)
{
List<Task> allTasks = new List<Task>();
//Limits Concurrent Downloads
SemaphoreSlim throttler = new SemaphoreSlim(initialCount: Properties.Settings.Default.maxConcurrentDownloads);
var urls = CreateUrls(fileIds);
foreach (var url in urls)
{
await throttler.WaitAsync();
allTasks.Add(Task.Run(async () =>
{
try
{
HttpClientHandler httpClientHandler = new HttpClientHandler();
if (!string.IsNullOrEmpty(securityCookieString))
{
Cookie securityCookie;
securityCookie = new Cookie(FormsAuthentication.FormsCookieName, securityCookieString);
securityCookie.Domain = securityCookieDomain;
httpClientHandler.CookieContainer.Add(securityCookie);
}
await DownloadFile(url, httpClientHandler).ConfigureAwait(false);
}
finally
{
throttler.Release();
}
}));
}
await Task.WhenAll(allTasks).ConfigureAwait(false);
}
async Task DownloadFile(string url, HttpClientHandler clientHandler)
{
HttpClient client = new HttpClient(clientHandler);
DownloadedFile downloadedFile = new DownloadedFile();
try
{
HttpResponseMessage responseMessage = await client.GetAsync(url).ConfigureAwait(false);
var byteArray = await responseMessage.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
if (responseMessage.Content.Headers.ContentDisposition != null)
{
downloadedFile.FileName = Path.Combine(Properties.Settings.Default.workingDirectory, responseMessage.Content.Headers.ContentDisposition.FileName);
}
else
{
return;
}
if (!Directory.Exists(Properties.Settings.Default.workingDirectory))
{
Directory.CreateDirectory(Properties.Settings.Default.workingDirectory);
}
using (FileStream filestream = new FileStream(downloadedFile.FileName, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
{
await filestream.WriteAsync(byteArray, 0, byteArray.Length);
}
}
catch(Exception ex)
{
return;
}
OnFileDownloaded(downloadedFile);
}
private void OnFileDownloaded(DownloadedFile downloadedFile)
{
if (this.FileDownloaded != null)
{
this.FileDownloaded(this, new DownloadedEventArgs(downloadedFile));
}
}
public class DownloadedEventArgs : EventArgs
{
public DownloadedEventArgs(DownloadedFile downloadedFile)
{
DownloadedFile = downloadedFile;
}
public DownloadedFile DownloadedFile { get; set; }
}
将Async / Await嵌入其他Async / Await方法有什么影响? (将文件流写入Async / Await方法内的磁盘。
是否正确使用了ConfigureAwait(False)还是应该更自由地使用它?
应该为每个单独的任务使用httpclient还是应该共享一个httpclient? 4.一个事件是将下载的文件引用“发送”到视图模型的一种好方法吗?
是await Task.WhenAll(allTasks).ConfigureAwait(false);同时运行所有任务的最佳方法是有限的通过SemaphoreSlim节气门。
#1 楼
首先,一些较小的事情(就代码的大小而言):我认为您应该多使用
var
,尤其是在清楚对象具有什么类型的情况下,因为只是创建它。您不应该将响应读取为单字节数组。相反,您应该尽可能使用
Stream
s(在您的情况下当然可以),因为它们效率更高。不仅要忽略未知异常。如果您要忽略某些例外情况(例如,当网站返回404时),请明确指定它们。结果。虽然使用
Semaphore
限制代码工作正常,但我认为您应该让一些用于异步处理集合的库为您做到这一点。这样的库包括Rx和TPL数据流。这样做还可以避免使用事件,因为您的方法将返回“异步集合”(Rx中的IObservable<T>
,TPL Dataflow中的ISourceBlock<T>
)。使用TPL Dataflow,您的代码可能看起来像:
public ISourceBlock<DownloadedFile> DownloadFiles(string[] fileIds, string securityCookieString, string securityCookieDomain)
{
var urls = CreateUrls(fileIds);
// we have to use TransformManyBlock here, because we want to be able to return 0 or 1 items
var block = new TransformManyBlock<string, DownloadedFile>(
async url =>
{
var httpClientHandler = new HttpClientHandler();
if (!string.IsNullOrEmpty(securityCookieString))
{
var securityCookie = new Cookie(FormsAuthentication.FormsCookieName, securityCookieString);
securityCookie.Domain = securityCookieDomain;
httpClientHandler.CookieContainer.Add(securityCookie);
}
return await DownloadFile(url, httpClientHandler);
}, new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = Properties.Settings.Default.maxConcurrentDownloads });
foreach (var url in urls)
block.Post(url);
block.Complete();
return block;
}
private static async Task<DownloadedFile[]> DownloadFile(string url, HttpClientHandler clientHandler)
{
var client = new HttpClient(clientHandler);
var downloadedFile = new DownloadedFile();
try
{
HttpResponseMessage responseMessage = await client.GetAsync(url);
if (responseMessage.Content.Headers.ContentDisposition == null)
return new DownloadedFile[0];
downloadedFile.FileName = Path.Combine(
Properties.Settings.Default.workingDirectory, responseMessage.Content.Headers.ContentDisposition.FileName);
if (!Directory.Exists(Properties.Settings.Default.workingDirectory))
{
Directory.CreateDirectory(Properties.Settings.Default.workingDirectory);
}
using (var httpStream = await responseMessage.Content.ReadAsStreamAsync())
using (var filestream = new FileStream(
downloadedFile.FileName, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
{
await httpStream.CopyToAsync(filestream, 4096);
}
}
// TODO: improve
catch (Exception ex)
{
return new DownloadedFile[0];
}
return new[] { downloadedFile };
}
#2 楼
抱歉,我没有仔细阅读您的代码,而是针对“新的Async和Await功能的真实示例”:基本上,在他们的新API中,所有可能很费时的事物(磁盘IO,网络IO等)现在都异步了。您只是无法同步调用它。
我认为这旨在迫使我们编写不阻止UI线程的响应式应用。
例如,您需要下载文件并保存它到磁盘,并在UI中显示忙碌指示器。然后,在ViewModel中,您可以说:
void DoTheJob()
{
IsBusy=true;
var text = await DownloadText();
ProgressText="Saving...";
await SaveText(text);
IsBusy=false;
}
我想说的是一个很好的现实示例(我的“字典”应用程序使用这种方法从磁盘)。
与旧的BackgroundWorkers / ThreadPool和Dispatcher.BeginInvoke麻烦相比,这是一种非常酷的UI处理方法。
评论
\ $ \ begingroup \ $
请注意,要编译代码,您需要在方法中添加async修饰符。此外,最好将返回类型更改为Task,以便可以依次等待您的方法。
\ $ \ endgroup \ $
– svick
2012年11月13日20:40
评论
\ $ \ begingroup \ $
感谢您提供的代码。发布样本时,我倾向于不使用var。我当时认为流是一种比字节数组更好的消费方式,但是我担心异步的性质。我看到您的代码将如何完美地处理流。最后-似乎我需要更深入地研究Rx和TPL。我以为他们对Async / Await有点过时了,但似乎我弄错了。两者的新版本。您会为这种情况推荐TPL吗?
\ $ \ endgroup \ $
–布兰德·邦德森(Blane Bunderson)
2012年11月20日在1:03
\ $ \ begingroup \ $
TPL肯定不会因异步方而过时,因为异步实际上是基于TPL(任务类型)构建的,部分原因是它们在做不同的事情(例如,没有Parallel.ForEach的直接异步替代品) ())。但是我专门谈论的是TPL Dataflow,它是.Net 4.5中的新增功能,其编写目的是为了利用异步。而且Rx也不是过时的,它的用法与普通异步方法大不相同(尽管有一些重叠)。
\ $ \ endgroup \ $
– svick
2012年11月20日,1:15
\ $ \ begingroup \ $
您是否没有通过HttpCompletionOption.ResponseHeadersRead使用此GetAsync重载,然后通过ReadAsStreamAsync()使用响应的任何原因?
\ $ \ endgroup \ $
– G. Stoynev
2013年12月18日16:43
\ $ \ begingroup \ $
@ G.Stoynev我刚刚从问题中复制了该代码。我实际上不太了解HttpClient。
\ $ \ endgroup \ $
– svick
13年12月18日在17:23
\ $ \ begingroup \ $
@AceInfinity我认为您的建议很好,但是由于许多建议都在修改我从问题中复制的代码,因此我认为您应该将其作为单独的评论答复发布。
\ $ \ endgroup \ $
– svick
16年4月25日在18:40