バックグラウンド
1 つの特定のホストからのコンテンツを使用してバッチ HTML ページ処理を実行するコードがあります。を使用して多数 (~400) の同時 HTTP リクエストを作成しようとしますHttpClient
。同時接続の最大数はによって制限されていると思われるServicePointManager.DefaultConnectionLimit
ため、独自の同時実行制限を適用していません。
HttpClient
すべての要求をusingに非同期で送信した後、andTask.WhenAll
を使用してバッチ操作全体をキャンセルできます。操作の進行状況はユーザー インターフェイスを介して表示でき、ボタンをクリックしてキャンセルを実行できます。CancellationTokenSource
CancellationToken
問題
CancellationTokenSource.Cancel()
約 5 ~ 30 秒間ブロックする呼び出し。これにより、ユーザー インターフェイスがフリーズします。メソッドがキャンセル通知用に登録したコードを呼び出しているために、これが発生したと思われます。
私が考えたこと
- 同時 HTTP 要求タスクの数を制限します。
HttpClient
すでに過剰なリクエスト自体をキューに入れているように見えるため、これを回避策と考えています。 CancellationTokenSource.Cancel()
非 UI スレッドでメソッド呼び出しを実行する。これはうまくいきませんでした。他のほとんどのタスクが完了するまで、タスクは実際には実行されませんでした。async
メソッドのバージョンがうまく機能すると思いますが、見つかりませんでした。また、UIスレッドでメソッドを使うのにも向いている印象です。
デモンストレーション
コード
class Program
{
private const int desiredNumberOfConnections = 418;
static void Main(string[] args)
{
ManyHttpRequestsTest().Wait();
Console.WriteLine("Finished.");
Console.ReadKey();
}
private static async Task ManyHttpRequestsTest()
{
using (var client = new HttpClient())
using (var cancellationTokenSource = new CancellationTokenSource())
{
var requestsCompleted = 0;
using (var allRequestsStarted = new CountdownEvent(desiredNumberOfConnections))
{
Action reportRequestStarted = () => allRequestsStarted.Signal();
Action reportRequestCompleted = () => Interlocked.Increment(ref requestsCompleted);
Func<int, Task> getHttpResponse = index => GetHttpResponse(client, cancellationTokenSource.Token, reportRequestStarted, reportRequestCompleted);
var httpRequestTasks = Enumerable.Range(0, desiredNumberOfConnections).Select(getHttpResponse);
Console.WriteLine("HTTP requests batch being initiated");
var httpRequestsTask = Task.WhenAll(httpRequestTasks);
Console.WriteLine("Starting {0} requests (simultaneous connection limit of {1})", desiredNumberOfConnections, ServicePointManager.DefaultConnectionLimit);
allRequestsStarted.Wait();
Cancel(cancellationTokenSource);
await WaitForRequestsToFinish(httpRequestsTask);
}
Console.WriteLine("{0} HTTP requests were completed", requestsCompleted);
}
}
private static void Cancel(CancellationTokenSource cancellationTokenSource)
{
Console.Write("Cancelling...");
var stopwatch = Stopwatch.StartNew();
cancellationTokenSource.Cancel();
stopwatch.Stop();
Console.WriteLine("took {0} seconds", stopwatch.Elapsed.TotalSeconds);
}
private static async Task WaitForRequestsToFinish(Task httpRequestsTask)
{
Console.WriteLine("Waiting for HTTP requests to finish");
try
{
await httpRequestsTask;
}
catch (OperationCanceledException)
{
Console.WriteLine("HTTP requests were cancelled");
}
}
private static async Task GetHttpResponse(HttpClient client, CancellationToken cancellationToken, Action reportStarted, Action reportFinished)
{
var getResponse = client.GetAsync("http://www.google.com", cancellationToken);
reportStarted();
using (var response = await getResponse)
response.EnsureSuccessStatusCode();
reportFinished();
}
}
出力
キャンセルが長時間ブロックされるのはなぜですか? また、私が間違っていること、または改善できることはありますか?