取消对静态 HttpClient 的异步调用

Canceling an async call on a static HttpClient

我正在使用静态 HttpClient(出于可扩展性原因 - 请参阅 )并且希望能够取消花费太长时间的单个请求。 SendAsync 上有一个重载需要 CancellationToken - 但我不知道它是否线程安全,因为我的 HttpClient 实例是 static。例如,如果我有多个请求同时通过 HttpClient 发送,我试图取消一个,它是否取消了正确的请求?

我查看了 HttpClient 代码,乍一看它看起来不像是线程安全的,因为取消被发送到 HttpClientHandler(这是相同的对于所有请求)。但我可能会遗漏一些东西。所以我的问题是:

  1. 我可以取消静态 HttpClient 上的单个请求吗?
  2. 如果没有,我该如何完成?

注意: 由于测试这个,需要一种方法来可靠地创建竞争条件,在我无法控制的代码中,我看不到一种方法测试一下。

每个 SendAsync 调用彼此完全独立,取消一个请求的令牌不会取消其他未完成的请求。

您假设因为 HttpClientHandler 为所有请求共享,这意味着所有请求都被取消是不正确的。如果您查看 HttpClientHandler 的反编译源代码,您会看到

[__DynamicallyInvokable]
protected internal override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
  if (request == null)
    throw new ArgumentNullException(nameof (request), SR.net_http_handler_norequest);
  this.CheckDisposed();
  if (Logging.On)
    Logging.Enter(Logging.Http, (object) this, nameof (SendAsync), (object) request);
  this.SetOperationStarted();
  TaskCompletionSource<HttpResponseMessage> completionSource = new TaskCompletionSource<HttpResponseMessage>();
  HttpClientHandler.RequestState state = new HttpClientHandler.RequestState();
  state.tcs = completionSource;
  state.cancellationToken = cancellationToken;
  state.requestMessage = request;
  try
  {
    HttpWebRequest prepareWebRequest = this.CreateAndPrepareWebRequest(request);
    state.webRequest = prepareWebRequest;
    cancellationToken.Register(HttpClientHandler.onCancel, (object) prepareWebRequest);
    if (ExecutionContext.IsFlowSuppressed())
    {
      IWebProxy webProxy = (IWebProxy) null;
      if (this.useProxy)
        webProxy = this.proxy ?? WebRequest.DefaultWebProxy;
      if (this.UseDefaultCredentials || this.Credentials != null || webProxy != null && webProxy.Credentials != null)
        this.SafeCaptureIdenity(state);
    }
    Task.Factory.StartNew(this.startRequest, (object) state);
  }
  catch (Exception ex)
  {
    this.HandleAsyncException(state, ex);
  }
  if (Logging.On)
    Logging.Exit(Logging.Http, (object) this, nameof (SendAsync), (object) completionSource.Task);
  return completionSource.Task;
}

每次调用 SendAsnyc 时,取消令牌都被包裹在一个新的 HttpClientHandler.RequestState state 对象中,当取消令牌时,只有与该状态对象关联的 state.webRequest 是那个将被取消。

刚刚得到 Microsoft 产品团队的确认:

Yes, it is completely safe to cancel an individual request using the cancellation token passed into the various HttpClient.SendAsync, .GetAsync, etc. methods. It does not matter that the HttpClient is "static". The cancellation token passed into the method is used for that particular request only.