3

私は、.net4.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. イベントは、ダウンロードしたファイル参照をビューモデルに「送信」するための良い方法ですか?[コードレビューにも投稿します]
4

2 に答える 2

2

async await を埋め込む場合は、使用する必要があります

Task.ConfigureAwait(false)

それ以外の場合、タスクは UI スレッドを除いて不要な呼び出し元のスレッド コンテキストで続行されます。要約すると、ライブラリは ConfigureAwait(false) を使用する必要があり、UI コードは使用しないでください。それはそれについてです!

于 2012-11-13T14:01:42.437 に答える
0

あなたの質問はあなたのコードに直接関係していないと思うので、ここで答えます:

他のAsync/AwaitメソッドにAsync/Awaitを埋め込むとどのような効果がありますか?(Async / Awaitメソッド内でファイルストリームをディスクに書き込みます。)

asyncメソッドは、このように組み合わせることが意図されています。実際には、それが唯一のことですasync-await非同期メソッドを組み合わせて別の非同期メソッドを作成するために使用できます。

まだ終了していない場合、メソッドは実際に呼び出し元に戻りますawaitTask次に、Task完了すると、メソッドは元のコンテキスト(たとえば、UIアプリケーションのUIスレッド)で再開されます。

元のコンテキストを続行したくない場合(必要がないため)、ConfigureAwait(false)すでに行っているように、を使用して変更できます。Task.Run()そのコードは元のコンテキストで実行されないため、内部でこれを行う必要はありません。

httpclientを個別のタスクごとに使用する必要がありますか、それとも単一のタスクを共有する必要がありますか?

のドキュメントにHttpClientは、そのインスタンスメソッドはスレッドセーフではないため、ごとに個別のインスタンスを使用する必要があると記載されていますTask

イベントは、ダウンロードしたファイル参照をビューモデルに「送信」するための良い方法ですか?

asyncイベントは-とうまく合わないと思いますawait。あなたの場合、それはあなたが使用BindingOperations.EnableCollectionSynchronizationし、あなた自身のコードでコレクションを適切にロックしている場合にのみ機能します。

より良い代替案は、TPLDataflowやRxのようなものを使用することだと思います。

于 2012-11-13T21:14:58.150 に答える