質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

Q&A

解決済

4回答

7359閲覧

C#でのWebClientでの同時ダウンロード数を制限したい。

NarugaL_ove

総合スコア108

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

0グッド

0クリップ

投稿2016/05/03 11:53

.netでの非同期処理についての質問です。

WebclientのDownloadFileAsyncで多数のファイルを非同期的に同時ダウンロードしたいのですが、同時にダウンロードできる個数を制限したいのです。

SemaphoreSlimクラスを使い制限しようとしたのですが、DownloadFileメソッドが同期的な場合、Semaphoreはちゃんと動きますが、非同期的処理になってくれません(UIが固まります)。非同期的なDownloadFileAsyncメソッドを使うと、セマフォーがうまく機能してくれません。

以下のコードでは、セマフォーの個数制限を3にしているにもかかわらず、タスクが1個ずつ順次に実行されてしまいます。

async/awaitキーワードや、Taskクラス等、情報がうまくまとまらず、混乱しています。どなたか助言してくださると幸いです。

C#

1//Main code... 2var ss = new SemaphoreSlim(3,3); 3foreach(var file in files) 4{ 5 await ss.WaitAsync(); 6 Task t = file.Download(); 7 await t; 8 ss.Release(); 9} 10 11~~~ 12//File Classのダウンロードメソッド 13public Task File.Download() 14{ 15 var wc = new WebClient(); 16 return wc.DownloadFileTaskAsync(this.url,this.name); 17} 18 19 20

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

ベストアンサー

配列やListの要素に対して並列処理をするなら、Parallelクラスを使った方が簡単になります。ParallelOptionsで同時実行する最大数も指定できるのでセマフォを使う必要もありません。
また、asyncメソッド内で時間のかかる処理はawait Task.Runで実行してください。

こんな感じ

C#

1await Task.Run(() => 2{ 3 Parallel.ForEach(files, new ParallelOptions { MaxDegreeOfParallelism = 3 }, (file) => 4 { 5 // この中は別スレッドで実行されるので同期で良い 6 file.Download().Wait(); 7 }); 8});

投稿2016/05/03 23:53

編集2016/05/04 00:01
catsforepaw

総合スコア5938

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

dekaaki

2016/05/04 00:43

Parallel・・・勉強不足でした. 自分の提示した方法より明らかに分かりやすくていいですね
NarugaL_ove

2016/05/04 13:38

ありがとうございます。解りやすく、シンプルに動いたのでBAにさせていただきます。
guest

0

一度にすべての実装をしようと考えると難しくなるので分割して考えましょう。
ちょっとやり方をかなり変えてしまいますがこんな感じでどうでしょうか?

1.10秒間固まってしまう処理をUIが応答できるように実装

c#

1-- 画面側 -- 2 3 // 非同期でDownloadメソッドを実行する 4 private async void button1_Click(object sender, EventArgs e) { 5 var result = await Task.Run(() => FileDownloader.Download()); 6 } 7 8 9-- ダウンロード処理側 -- 10 public class FileDownloader { 11 12 private static List<string> Download() { 13 Thread.Sleep(10 * 1000); 14 return null; 15 } 16 17 }

2.画面側はこれで完成なので後はダウンロード処理を実装していきます。
次の処理としては既に非同期で処理はできているので5つのファイルを順次ダウンロードするようにします。

c#

1 -- ダウンロード処理側 -- 2 public class FileDownloader { 3 4 public class Target { 5 public Target(string url, string name) { 6 this.Url = url; 7 this.Name = name; 8 } 9 10 public string Url { set; get; } 11 public string Name { set; get; } 12 } 13 14 public static List<string> Download() { 15 16 var targets = new List<Target>(); 17 targets.Add(new Target("http://hoge/file1.aspx", "file1.txt")); 18 targets.Add(new Target("http://hoge/file2.aspx", "file2.txt")); 19 targets.Add(new Target("http://hoge/file3.aspx", "file3.txt")); 20 targets.Add(new Target("http://hoge/file4.aspx", "file4.txt")); 21 targets.Add(new Target("http://hoge/file5.aspx", "file5.txt")); 22 23 var result = new List<string>(); 24 25 var wc = new WebClient(); 26 foreach (var target in targets) { 27 wc.DownloadFile(target.Url, target.Name); 28 result.Add(target.Name); 29 } 30 31 return result; 32 } 33 34 }

3.今度はDownloadメソッド内で全て同時にダウンロードできるようにします。

c#

1 var tasks = new List<Task<string>>(); 2 3 foreach (var target in targets) { 4 5 // 非同期でダウンロードを実行 6 tasks.Add(Task.Factory.StartNew<string>(() => { 7 var wc = new WebClient(); 8 wc.DownloadFile(target.Url, target.Name); 9 return target.Name; 10 })); 11 12 } 13 14 // 上記で作成したタスク全ての完了を待つ 15 Task.WaitAll(tasks.ToArray()); 16 17 // タスクの実行結果を返す 18 return tasks.Select(x => x.Result).ToList();

4.最後にSemaphoreSlimを使用して同時実行数を制限します。

c#

1 private static SemaphoreSlim LimitControl = new SemaphoreSlim(3, 3); 2 3 LimitControl.Wait(); 4 5 try { 6 var wc = new WebClient(); 7 wc.DownloadFile(target.Url, target.Name); 8 } finally { 9 LimitControl.Release(); 10 }

最終的にはこんな感じでどうでしょう?

C#

1--- 画面側 --- 2 // 非同期でDownloadメソッドを実行する 3 private async void button1_Click(object sender, EventArgs e) { 4 5 var targets = new List<FileDownloader.Target>(); 6 targets.Add(new FileDownloader.Target("http://hoge/file1.aspx", "file1.txt")); 7 targets.Add(new FileDownloader.Target("http://hoge/file2.aspx", "file2.txt")); 8 targets.Add(new FileDownloader.Target("http://hoge/file3.aspx", "file3.txt")); 9 targets.Add(new FileDownloader.Target("http://hoge/file4.aspx", "file4.txt")); 10 targets.Add(new FileDownloader.Target("http://hoge/file5.aspx", "file5.txt")); 11 12 var result = await Task.Run(() => FileDownloader.Download(targets)); 13 } 14 15--- ダウンロード側 --- 16 public class FileDownloader { 17 18 private static SemaphoreSlim LimitControl = new SemaphoreSlim(3, 3); 19 20 public class Target { 21 public Target(string url, string name) { 22 this.Url = url; 23 this.Name = name; 24 } 25 26 public string Url { set; get; } 27 public string Name { set; get; } 28 } 29 30 31 public static List<string> Download(IEnumerable<Target> targets) { 32 33 var tasks = new List<Task<string>>(); 34 35 foreach (var target in targets) { 36 37 // 非同期でダウンロードを実行 38 tasks.Add(Task.Factory.StartNew<string>(() => { 39 40 LimitControl.Wait(); 41 42 try { 43 var wc = new WebClient(); 44 wc.DownloadFile(target.Url, target.Name); 45 } finally { 46 LimitControl.Release(); 47 } 48 49 return target.Name; 50 })); 51 52 } 53 54 // 上記で作成したタスク全ての完了を待つ 55 Task.WaitAll(tasks.ToArray()); 56 57 // タスクの実行結果を返す 58 return tasks.Select(x => x.Result).ToList(); 59 } 60 61 }

投稿2016/05/03 15:53

dekaaki

総合スコア292

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

NarugaL_ove

2016/05/05 02:34

ありがとうございます。このようなやり方もあるのですね。
guest

0

こんにちは。

await/asyncを実際に使ったことはないので、外していたらすいません。

まず、await ss.WaitAsync();ss.Release();をコメントアウトし、かつ、ss.Relase();の位置に何かの実行文を置いた時、ダウンロードは並列に実行されますでしょうか?

forループの中でawaitを使った例を見たことがないので、このような使い方をした時のawaitの振る舞いが分かりませんが、taskが終了するまでawaitの次の文以降が実行されない筈ですから、単純にforループがブロックされそうな印象です。
forループの中身を丸っとasync関数化する必要があるのではないでしょうか?

つまり、Task File.Download()をasync関数とし、await ss.WaitAsync();ss.Release();をこの中へ移動する必要があるように思えます。
そして、非同期:awaitを含むコードをロックするには?を見ると、try/finallyで括って置かないと、Download中に例外が発生するとセマフォが開放されなくなりそうです。

投稿2016/05/03 15:27

編集2016/05/03 15:37
Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

NarugaL_ove

2016/05/05 02:35

ありがとうございます。 >taskが終了するまでawaitの次の文以降が実行されない筈ですから、単純にforループがブロックされそうな印象です。 自分のコードがうまく動かなかった原因がわかりました。勉強になります。
guest

0

以下のようなコードでどうでしょうか。

lang

1using System.Linq; 2 3var ss = new SemaphoreSlim(3, 3); 4await Task.All(files.Select(async x => 5{ 6 await ss.WaitAsync(); 7 await file.Download(); 8 ss.Release(); 9});

LINQ の Select を使用して、files のそれぞれの要素を、セマフォを待機してファイルを非同期的にダウンロードする Task に変換して、その結果を Task.All で待機します。
async を使用して、ラムダ式自体を非同期にするのがコツです。

投稿2016/05/03 15:15

chitoku

総合スコア1610

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

NarugaL_ove

2016/05/05 02:37

ありがとうございます。勉強になりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問