C#でのWebClientでの同時ダウンロード数を制限したい。
解決済
回答 4
投稿
- 評価
- クリップ 0
- VIEW 4,465
.netでの非同期処理についての質問です。
WebclientのDownloadFileAsyncで多数のファイルを非同期的に同時ダウンロードしたいのですが、同時にダウンロードできる個数を制限したいのです。
SemaphoreSlimクラスを使い制限しようとしたのですが、DownloadFileメソッドが同期的な場合、Semaphoreはちゃんと動きますが、非同期的処理になってくれません(UIが固まります)。非同期的なDownloadFileAsyncメソッドを使うと、セマフォーがうまく機能してくれません。
以下のコードでは、セマフォーの個数制限を3にしているにもかかわらず、タスクが1個ずつ順次に実行されてしまいます。
async/awaitキーワードや、Taskクラス等、情報がうまくまとまらず、混乱しています。どなたか助言してくださると幸いです。
//Main code...
var ss = new SemaphoreSlim(3,3);
foreach(var file in files)
{
await ss.WaitAsync();
Task t = file.Download();
await t;
ss.Release();
}
~~~
//File Classのダウンロードメソッド
public Task File.Download()
{
var wc = new WebClient();
return wc.DownloadFileTaskAsync(this.url,this.name);
}
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+2
配列やListの要素に対して並列処理をするなら、Parallel
クラスを使った方が簡単になります。ParallelOptions
で同時実行する最大数も指定できるのでセマフォを使う必要もありません。
また、asyncメソッド内で時間のかかる処理はawait Task.Run
で実行してください。
こんな感じ
await Task.Run(() =>
{
Parallel.ForEach(files, new ParallelOptions { MaxDegreeOfParallelism = 3 }, (file) =>
{
// この中は別スレッドで実行されるので同期で良い
file.Download().Wait();
});
});
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
0
以下のようなコードでどうでしょうか。
using System.Linq;
var ss = new SemaphoreSlim(3, 3);
await Task.All(files.Select(async x =>
{
await ss.WaitAsync();
await file.Download();
ss.Release();
});
LINQ の Select
を使用して、files のそれぞれの要素を、セマフォを待機してファイルを非同期的にダウンロードする Task
に変換して、その結果を Task.All
で待機します。
async
を使用して、ラムダ式自体を非同期にするのがコツです。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
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中に例外が発生するとセマフォが開放されなくなりそうです。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
0
一度にすべての実装をしようと考えると難しくなるので分割して考えましょう。
ちょっとやり方をかなり変えてしまいますがこんな感じでどうでしょうか?
1.10秒間固まってしまう処理をUIが応答できるように実装
-- 画面側 --
// 非同期でDownloadメソッドを実行する
private async void button1_Click(object sender, EventArgs e) {
var result = await Task.Run(() => FileDownloader.Download());
}
-- ダウンロード処理側 --
public class FileDownloader {
private static List<string> Download() {
Thread.Sleep(10 * 1000);
return null;
}
}
2.画面側はこれで完成なので後はダウンロード処理を実装していきます。
次の処理としては既に非同期で処理はできているので5つのファイルを順次ダウンロードするようにします。
-- ダウンロード処理側 --
public class FileDownloader {
public class Target {
public Target(string url, string name) {
this.Url = url;
this.Name = name;
}
public string Url { set; get; }
public string Name { set; get; }
}
public static List<string> Download() {
var targets = new List<Target>();
targets.Add(new Target("http://hoge/file1.aspx", "file1.txt"));
targets.Add(new Target("http://hoge/file2.aspx", "file2.txt"));
targets.Add(new Target("http://hoge/file3.aspx", "file3.txt"));
targets.Add(new Target("http://hoge/file4.aspx", "file4.txt"));
targets.Add(new Target("http://hoge/file5.aspx", "file5.txt"));
var result = new List<string>();
var wc = new WebClient();
foreach (var target in targets) {
wc.DownloadFile(target.Url, target.Name);
result.Add(target.Name);
}
return result;
}
}
3.今度はDownloadメソッド内で全て同時にダウンロードできるようにします。
var tasks = new List<Task<string>>();
foreach (var target in targets) {
// 非同期でダウンロードを実行
tasks.Add(Task.Factory.StartNew<string>(() => {
var wc = new WebClient();
wc.DownloadFile(target.Url, target.Name);
return target.Name;
}));
}
// 上記で作成したタスク全ての完了を待つ
Task.WaitAll(tasks.ToArray());
// タスクの実行結果を返す
return tasks.Select(x => x.Result).ToList();
4.最後にSemaphoreSlimを使用して同時実行数を制限します。
private static SemaphoreSlim LimitControl = new SemaphoreSlim(3, 3);
LimitControl.Wait();
try {
var wc = new WebClient();
wc.DownloadFile(target.Url, target.Name);
} finally {
LimitControl.Release();
}
最終的にはこんな感じでどうでしょう?
--- 画面側 ---
// 非同期でDownloadメソッドを実行する
private async void button1_Click(object sender, EventArgs e) {
var targets = new List<FileDownloader.Target>();
targets.Add(new FileDownloader.Target("http://hoge/file1.aspx", "file1.txt"));
targets.Add(new FileDownloader.Target("http://hoge/file2.aspx", "file2.txt"));
targets.Add(new FileDownloader.Target("http://hoge/file3.aspx", "file3.txt"));
targets.Add(new FileDownloader.Target("http://hoge/file4.aspx", "file4.txt"));
targets.Add(new FileDownloader.Target("http://hoge/file5.aspx", "file5.txt"));
var result = await Task.Run(() => FileDownloader.Download(targets));
}
--- ダウンロード側 ---
public class FileDownloader {
private static SemaphoreSlim LimitControl = new SemaphoreSlim(3, 3);
public class Target {
public Target(string url, string name) {
this.Url = url;
this.Name = name;
}
public string Url { set; get; }
public string Name { set; get; }
}
public static List<string> Download(IEnumerable<Target> targets) {
var tasks = new List<Task<string>>();
foreach (var target in targets) {
// 非同期でダウンロードを実行
tasks.Add(Task.Factory.StartNew<string>(() => {
LimitControl.Wait();
try {
var wc = new WebClient();
wc.DownloadFile(target.Url, target.Name);
} finally {
LimitControl.Release();
}
return target.Name;
}));
}
// 上記で作成したタスク全ての完了を待つ
Task.WaitAll(tasks.ToArray());
// タスクの実行結果を返す
return tasks.Select(x => x.Result).ToList();
}
}
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.32%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2016/05/04 09:43
自分の提示した方法より明らかに分かりやすくていいですね
2016/05/04 22:38