我一直在寻找在.net 4.5中找到新的Async和Await功能的真实世界示例。我已经提出以下代码来下载文件列表并限制并发下载的数量。我将不胜感激任何最佳实践或方法来改进/优化此代码。
我们使用以下语句调用以下代码。
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; }
}
在Svick的建议之后 - 以下是直接问题:
答案 0 :(得分:2)
如果嵌入异步等待,则应使用
Task.ConfigureAwait(false)
在任何返回任务的情况下,否则任务将继续在调用者的线程上下文中,除了在UI线程上之外是不必要的。总之,libaries应该使用ConfigureAwait(false),UI代码不应该使用。就是这个!
答案 1 :(得分:0)
我认为您的问题与您的代码没有直接关系,所以我会在这里回答:
在其他Async / Await方法中嵌入Async / Await有什么影响? (在Async / Await方法中将文件流写入磁盘。)
async
方法应该像这样组合。实际上,async
- await
唯一可以用来:结合异步方法来创建另一个异步方法。
如果您await
Task
尚未完成,您的方法实际上会返回给调用者。然后,当Task
完成时,您的方法将在原始上下文中恢复(例如UI应用程序中的UI线程)。
如果您不想继续使用原始上下文(因为您不需要它),可以像使用ConfigureAwait(false)
一样更改它。没有必要在Task.Run()
内执行此操作,因为该代码不会在原始上下文中运行。
httpclient是应该用于每个单独的任务还是应该共享一个?
HttpClient
的文档说它的实例方法不是线程安全的,所以你应该为每个Task
使用单独的实例。
事件是将下载的文件引用“发送”到viewmodel的好方法吗?
我认为事件与async
- await
并不匹配。在您的情况下,只有在您使用BindingOperations.EnableCollectionSynchronization
并且还在您自己的代码中正确锁定集合时,它才会起作用。
我认为更好的选择是使用像TPL Dataflow或Rx这样的东西。