現在、C# でコンソールアプリケーション(.NET Framework 4.8)を作っています。
このプログラムでは複数のURLへ並列・非同期で問い合わせを行いたいのですが、
並列で処理をすることができずに困っています。
C#のコードは以下のようになります
c#
1static async Task Main(string[] args) 2{ 3 var encoding = new UTF8Encoding(false); 4 var tasks = new List<Task>(); 5 6 // 5つの問い合わせを行う 7 foreach (var i in Enumerable.Range(0, 5)) 8 { 9 var task = Task.Run(async () => 10 { 11 // 応答までに10秒かかるURL 12 var uri = "https://example.com/sample.php"; 13 var request = (HttpWebRequest)WebRequest.Create(uri); 14 using (var response = await request.GetResponseAsync()) 15 using (var stream = response.GetResponseStream()) 16 using (var memory = new System.IO.MemoryStream()) 17 { 18 await stream.CopyToAsync(memory); 19 var text = encoding.GetString(memory.ToArray()); 20 Console.WriteLine(text); 21 } 22 }); 23 24 tasks.Add(task); 25 } 26 27 // すべて終わるまで待機 28 await Task.WhenAll(tasks); 29 30 Console.WriteLine("Finish"); 31 Console.ReadLine(); 32}
サーバー側はテストのため下記のようなコードを設置しています
php
1<?php // https://example.com/sample.php 2sleep(10); 3echo "OK";
上記の場合、感覚的には5つの問い合わせは合計10秒超で終わるイメージなのですが、
コンソールへの出力を見ると、順不同ですが結果はおおよそ10秒ずつ表示され、
すべて終わるまでに合計で50秒かかってしまいます。
上記の処理を並列・非同期で実行する方法についてご存知の方がいらっしゃいましたらご助言をいただけないでしょうか?
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答3件
0
ベストアンサー
もしサーバー側に問題がなければServicePointManager.DefaultConnectionLimitを変更してみてください。
投稿2021/06/16 04:58
総合スコア443
0
【追記・訂正】
下の回答で「期待通り 5 件の要求が並列処理され、5 件の応答が約 10 秒で返ってきます」と書きましたが、それは要求先が localhost だったからでした。
HTTP 1.1 仕様で同時接続は 2 つまでとなっています。HttpWebRequest を使った場合にもその制約が適用されるようになっているようですが、ユーザーが ServicePointManager.DefaultConnectionLimit の設定を変えない場合かつ localhost への要求の場合は制約が外れます(Int32.MaxValue になる)。
ソースコードを見ると(URL 下記)そのコメントに "3. If ServicePoint.DefaultConnectionLimit is set, then take that value" "4. If ServicePoint is localhost, then set to infinite (TO Should we change this value?)" と書いてありますが、試してみるとその通りの動きをしました。
https://referencesource.microsoft.com/#System/net/System/Net/ServicePoint.cs
https://referencesource.microsoft.com/#System/net/System/Net/ServicePointManager.cs
ServicePoint クラスの ConnectionLimit プロパティの中の getter の return 文のところで同時接続数を Int32.MaxValue に設定していました。
ただし、localhost か否かの判定はどのタイミングでどのようにしているのか、何がどのタイミングで ConnectionLimit プロパティの getter を呼び出して同時接続数を Int32.MaxValue に設定しているのかはソースコードからは読み切れてません。
試しに、hosts ファイルで 127.0.0.1 にそれらしいホスト名を設定して、そのホスト名を url に設定した場合は同時接続数は 2 になりました。(と言うことは IP アドレスではなくて localhost という名前で判定?)
しかし、Fiddler を通してみると、Fiddler は IP アドレス 127.0.0.1 のプロキシなのですが、どこかで IP アドレスから localhost と判定されて、同時接続数は制限されないという結果になりました。
依然として ServicePointManager.DefaultConnectionLimit や ServicePoint.ConnectionLimit の値など、いろいろ不可解な動きに見えるのですが、とにかく localhost はデフォルトでは同時接続数は Int32.MaxValue になるということは間違いなさそうです。
質問者さんのコードをコピペし、スレッド ID と開始/終了時間が分かるようにコードを追加して試してみましたが、期待通り 5 件の要求が並列処理され、5 件の応答が約 10 秒で返ってきます。
なのて、tamoto さんが言われるようにサーバー側の問題のようです。サーバー側をチェックしてみてください。
ちなみに、サーバー側は ASP.NET MVC5 で開発マシンの IIS Express で動かしているもので、10 秒待って OK を返すものです。コードは以下の通りです。
public async Task<ActionResult> Sample() { await Task.Delay(10000); return Content("OK"); }
クライアント側は質問者さんのコードをコピペして使いました。それに以下のようにコメントで「// 質問者さんのコードに追加」というコードを、スレッド ID と開始/終了時間が分かるように追加しています。
using System; using System.Threading.Tasks; using System.Collections.Generic; using System.Net; using System.Linq; using System.Text; using System.Threading; namespace ConsoleAppAsync4 { class Program { static async Task Main(string[] args) { var encoding = new UTF8Encoding(false); var tasks = new List<Task>(); // 5つの問い合わせを行う foreach (var i in Enumerable.Range(0, 5)) { var task = Task.Run(async () => { // 質問者さんのコードに追加 int id = Thread.CurrentThread.ManagedThreadId; string start = $" / ThreadID = {id}, start: {DateTime.Now:ss.fff}, "; // 応答までに10秒かかるURL var uri = "https://localhost:44365/Home/sample"; var request = (HttpWebRequest)WebRequest.Create(uri); using (var response = await request.GetResponseAsync()) using (var stream = response.GetResponseStream()) using (var memory = new System.IO.MemoryStream()) { await stream.CopyToAsync(memory); var text = encoding.GetString(memory.ToArray()); // 質問者さんのコードに追加 string end = $"end: {DateTime.Now:ss.fff}"; text += start + end; Console.WriteLine(text); } }); tasks.Add(task); } // すべて終わるまで待機 await Task.WhenAll(tasks); Console.WriteLine("Finish"); Console.ReadLine(); } } }
【追記】
下の 2021/06/16 15:01 の私のコメントで「後で結果の画像を回答欄に貼っておきます」と書いた件です。
試してみましたが、自分のケースでも「ASP.NET でホストされるアプリケーションの場合は10、それ以外の場合は2」のそれ以外の場合に該当し 2 になっていました。でも、そのために秒単位で待たされるということなないです。どのように確認したかというと、
上のコードはそのまま、ServicePointManager.DefaultConnectionLimit の値を調べるため、以下の画像の赤枠の一行を追加。
結果は以下のようになります。質問者さんのケースのように秒の単位で待たされるということはないです。
tamoto さんの回答のコメントにある tamoto さんが試した結果も問題なかったようですし、ちょっと不可解ですね。
私の回答のコメントでも書きましたが、Fiddler などのキャプチャツールを使って、5 件の要求が一度に出て行っているかも調べてはいかがですか?
投稿2021/06/16 04:43
編集2021/06/17 07:03退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2021/06/16 05:18
2021/06/16 05:27
退会済みユーザー
2021/06/16 05:40
退会済みユーザー
2021/06/16 06:01
2021/06/16 06:15 編集
退会済みユーザー
2021/06/16 06:20
2021/06/16 06:35
2021/06/16 06:39 編集
退会済みユーザー
2021/06/16 07:34
退会済みユーザー
2021/06/16 07:48 編集
2021/06/16 08:01
退会済みユーザー
2021/06/16 08:04
2021/06/16 08:38
退会済みユーザー
2021/06/16 09:34
0
こんにちは。
クライアント側のコードは問題ないように思います。
質問のコードは意図している通りに5件のリクエストを並列に処理しています。
なので、もしこれで50秒かかるのであれば、サーバ側に原因がある可能性が高いです。
PHP は全く詳しくないのですが、リクエストを一つずつ処理している可能性はないでしょうか。
サーバ側のリクエスト・レスポンスのログを確認してみてはいかがでしょうか。
投稿2021/06/16 03:16
総合スコア4228
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/06/16 04:39
2021/06/16 04:50
2021/06/16 04:58
2021/06/16 05:07 編集
2021/06/16 05:24
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/06/16 05:22
2021/06/16 05:53