取消对静态 HttpClient 的异步调用
Canceling an async call on a static HttpClient
我正在使用静态 HttpClient(出于可扩展性原因 - 请参阅 )并且希望能够取消花费太长时间的单个请求。 SendAsync
上有一个重载需要 CancellationToken
- 但我不知道它是否线程安全,因为我的 HttpClient
实例是 static
。例如,如果我有多个请求同时通过 HttpClient
发送,我试图取消一个,它是否取消了正确的请求?
我查看了 HttpClient
代码,乍一看它看起来不像是线程安全的,因为取消被发送到 HttpClientHandler
(这是相同的对于所有请求)。但我可能会遗漏一些东西。所以我的问题是:
- 我可以取消静态 HttpClient 上的单个请求吗?
- 如果没有,我该如何完成?
注意:
由于测试这个,需要一种方法来可靠地创建竞争条件,在我无法控制的代码中,我看不到一种方法测试一下。
每个 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.
我正在使用静态 HttpClient(出于可扩展性原因 - 请参阅 )并且希望能够取消花费太长时间的单个请求。 SendAsync
上有一个重载需要 CancellationToken
- 但我不知道它是否线程安全,因为我的 HttpClient
实例是 static
。例如,如果我有多个请求同时通过 HttpClient
发送,我试图取消一个,它是否取消了正确的请求?
我查看了 HttpClient
代码,乍一看它看起来不像是线程安全的,因为取消被发送到 HttpClientHandler
(这是相同的对于所有请求)。但我可能会遗漏一些东西。所以我的问题是:
- 我可以取消静态 HttpClient 上的单个请求吗?
- 如果没有,我该如何完成?
注意: 由于测试这个,需要一种方法来可靠地创建竞争条件,在我无法控制的代码中,我看不到一种方法测试一下。
每个 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 theHttpClient
is "static
". The cancellation token passed into the method is used for that particular request only.