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

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

ただいまの
回答率

90.48%

  • C#

    7404questions

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

  • .NET Framework

    478questions

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

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

解決済

回答 4

投稿

  • 評価
  • クリップ 0
  • VIEW 2,205

NarugaL_ove

score 98

.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ページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 4

checkベストアンサー

+2

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

こんな感じ

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/04 09:43

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

    キャンセル

  • 2016/05/04 22:38

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

    キャンセル

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 を使用して、ラムダ式自体を非同期にするのがコツです。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/05 11:37

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

    キャンセル

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/05 11:35

    ありがとうございます。
    >taskが終了するまでawaitの次の文以降が実行されない筈ですから、単純にforループがブロックされそうな印象です。

    自分のコードがうまく動かなかった原因がわかりました。勉強になります。

    キャンセル

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();
        }

    }

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/05/05 11:34

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

    キャンセル

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

  • ただいまの回答率 90.48%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • C#

    7404questions

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

  • .NET Framework

    478questions

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