title: Why does cancellation block for so long when cancelling a lot of HTTP requests?
body: <h2>Background</h2>
  
  <p>I have some code that performs batch HTML page processing using content from one specific host. It tries to make a large number (~400) of simultaneous HTTP requests using <code>HttpClient</code>. I believe that the maximum number of simultaneous connections is restricted by <code>ServicePointManager.DefaultConnectionLimit</code>, so I'm not applying my own concurrency restrictions.</p>
  
  <p>After sending all of the requests asynchronously to <code>HttpClient</code> using <code>Task.WhenAll</code>, the entire batch operation can be cancelled using <code>CancellationTokenSource</code> and <code>CancellationToken</code>. The progress of the operation is viewable via a user interface, and a button can be clicked to perform the cancellation.</p>
  
  <h2>Problem</h2>
  
  <p>The call to <code>CancellationTokenSource.Cancel()</code> blocks for roughly 5 - 30 seconds. This causes the user interface to freeze. Is suspect that this occurs because the method is calling the code that registered for cancellation notification.</p>
  
  <h2>What I've Considered</h2>
  
  <ol>
  <li>Limiting the number of simultaneous HTTP request tasks. I consider this a work-around because <code>HttpClient</code> already seems to queue excess requests itself.</li>
  <li>Performing the <code>CancellationTokenSource.Cancel()</code> method call in a non-UI thread. This didn't work too well; the task didn't actually run until most of the others had finished. I think an <code>async</code> version of the method would work well, but I couldn't find one. Also, I have the impression that it's suitable to use the method in a UI thread.</li>
  </ol>
  
  <h2>Demonstration</h2>
  
  <h3>Code</h3>
  
  <pre><code>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 = () =&gt; allRequestsStarted.Signal();
                  Action reportRequestCompleted = () =&gt; Interlocked.Increment(ref requestsCompleted);
                  Func&lt;int, Task&gt; getHttpResponse = index =&gt; 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();
      }
  }
  </code></pre>
  
  <h3>Output</h3>
  
  <p><img src="https://i.stack.imgur.com/puevB.png" alt="Console window showing that cancellation blocked for over 13 seconds"></p>
  
  <p><strong>Why does cancellation block for so long?</strong> Also, is there anything that I'm doing wrong or could be doing better?</p>