.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ページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答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総合スコア5938
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
総合スコア292
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
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総合スコア23272
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/05/05 02:35
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
総合スコア1610
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/05/04 00:43
2016/05/04 13:38