似乎 GetResponseAsync 不接受 Async/Await 中的 cancelToken。所以问题是如何取消以下程序,前提是我需要从响应中收集 Cookie:
using (HttpWebResponse response = (HttpWebResponse) await request.GetResponseAsync())
{
cookies.Add(response.Cookies);
}
实现上述目的的替代代码也是受欢迎的。
似乎 GetResponseAsync 不接受 Async/Await 中的 cancelToken。所以问题是如何取消以下程序,前提是我需要从响应中收集 Cookie:
using (HttpWebResponse response = (HttpWebResponse) await request.GetResponseAsync())
{
cookies.Add(response.Cookies);
}
实现上述目的的替代代码也是受欢迎的。
像这样的东西应该可以工作(未经测试):
public static class Extensions
{
public static async Task<HttpWebResponse> GetResponseAsync(this HttpWebRequest request, CancellationToken ct)
{
using (ct.Register(() => request.Abort(), useSynchronizationContext: false))
{
var response = await request.GetResponseAsync();
ct.ThrowIfCancellationRequested();
return (HttpWebResponse)response;
}
}
}
理论上,如果取消请求ct
并被request.Abort
调用,await request.GetResponseAsync()
应该抛出一个WebException
. 不过,IMO,在使用结果时明确检查取消总是一个好主意,以减轻竞争条件,所以我打电话给ct.ThrowIfCancellationRequested()
.
另外,我假设它request.Abort
是线程安全的(可以从任何线程调用),所以我使用useSynchronizationContext: false
(我还没有验证)。
[更新]以解决 OP 关于如何区分WebException
取消引起的错误和任何其他错误的评论。这就是它可以完成的方式,因此TaskCanceledException
(派生自OperationCanceledException
)将在取消时正确抛出:
public static class Extensions
{
public static async Task<HttpWebResponse> GetResponseAsync(this HttpWebRequest request, CancellationToken ct)
{
using (ct.Register(() => request.Abort(), useSynchronizationContext: false))
{
try
{
var response = await request.GetResponseAsync();
return (HttpWebResponse)response;
}
catch (WebException ex)
{
// WebException is thrown when request.Abort() is called,
// but there may be many other reasons,
// propagate the WebException to the caller correctly
if (ct.IsCancellationRequested)
{
// the WebException will be available as Exception.InnerException
throw new OperationCanceledException(ex.Message, ex, ct);
}
// cancellation hasn't been requested, rethrow the original WebException
throw;
}
}
}
}
public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken, Action action, bool useSynchronizationContext = true)
{
using (cancellationToken.Register(action, useSynchronizationContext))
{
try
{
return await task;
}
catch (Exception ex)
{
if (cancellationToken.IsCancellationRequested)
{
// the Exception will be available as Exception.InnerException
throw new OperationCanceledException(ex.Message, ex, cancellationToken);
}
// cancellation hasn't been requested, rethrow the original Exception
throw;
}
}
}
现在您可以在任何可取消的异步方法上使用您的取消令牌。例如 WebRequest.GetResponseAsync:
var request = (HttpWebRequest)WebRequest.Create(url);
using (var response = await request.GetResponseAsync())
{
. . .
}
会变成:
var request = (HttpWebRequest)WebRequest.Create(url);
using (WebResponse response = await request.GetResponseAsync().WithCancellation(CancellationToken.None, request.Abort, true))
{
. . .
}