【问题标题】:Real World Async and Await Code Example [closed]真实世界的异步和等待代码示例 [关闭]
【发布时间】:2012-11-14 05:14:58
【问题描述】:

我一直在到处寻找 .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 的建议 - 以下是直接问题:

  1. 在其他 Async/Await 方法中嵌入 Async/Await 有什么影响? (在 Async / Await 方法中将文件流写入磁盘。
  2. 应该为每个单独的任务使用一个 httpclient,还是应该共享一个?
  3. 事件是一种将下载的文件引用“发送”到视图模型的好方法吗? [我也会在 codereview 发帖]

【问题讨论】:

标签: c# async-await


【解决方案1】:

如果你嵌入异步等待你应该使用

Task.ConfigureAwait(false)

在任何返回 Task 的东西上,否则任务将在调用者的线程上下文中继续,这在 UI 线程上是不必要的。总之,库应该使用 ConfigureAwait(false) 而 UI 代码不应该。就是这样!

【讨论】:

    【解决方案2】:

    我认为您的问题与您的代码没有直接关系,所以我将在这里回答:

    在其他 Async/Await 方法中嵌入 Async/Await 有什么效果? (在 Async / Await 方法中将文件流写入磁盘。)

    async 方法应该像这样组合。实际上,只有 async-await 可以用来:组合异步方法来创建另一个异步方法。

    如果你 await Task 还没有完成,你的方法实际上会返回给调用者。然后,当Task 完成时,您的方法会在原始上下文(例如 UI 应用程序中的 UI 线程)上恢复。

    如果您不想继续使用原始上下文(因为您不需要它),您可以使用 ConfigureAwait(false) 更改它,就像您已经做的那样。无需在 Task.Run() 中执行此操作,因为该代码不会在原始上下文中运行。

    应该为每个单独的任务使用一个 httpclient,还是应该共享一个?

    HttpClient 的文档说它的实例方法不是线程安全的,因此您应该为每个 Task 使用单独的实例。

    事件是一种将下载的文件引用“发送”到视图模型的好方法吗?

    我认为事件与async-await 不太吻合。在您的情况下,它只有在您使用 BindingOperations.EnableCollectionSynchronization 并且还在您自己的代码中正确锁定集合时才有效。

    我认为更好的选择是使用 TPL Dataflow 或 Rx 之类的东西。

    【讨论】:

      猜你喜欢
      • 2012-03-27
      • 2016-01-31
      • 1970-01-01
      • 1970-01-01
      • 2010-12-24
      • 2019-07-22
      • 2010-11-23
      • 2021-06-16
      • 2018-06-23
      相关资源
      最近更新 更多