GetResponseAsync不接受cancellationToken。

22

看起来GetResponseAsync在Async/Await中不接受cancellationToken。那么问题是,如果我需要从响应中收集Cookies,我该如何取消下面的过程:

 using (HttpWebResponse response = (HttpWebResponse) await request.GetResponseAsync())
 {
    cookies.Add(response.Cookies);
 }

欢迎提供其他实现上述功能的代码。


1
你尝试过调用 Abort() 吗? - svick
我在https://dev59.com/5l7Va4cB1Zd3GeqPLqW1中看到过,但不确定如何正确实现。我将CancellationToken ct传递给该方法。 - Jim
为什么不使用 HttpClient - Paulo Morgado
好像需要更多的代码来编写... 无论如何,你为什么要选择HttpClient呢? - Jim
2个回答

40

这样的代码应该可以运行(未经测试):

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 异常。但是,在使用结果时明确检查取消状态以减少竞争条件总是一个好主意,因此我调用 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 GetResponseAsync( this HttpWebRequest request, CancellationToken ct) { using (ct.Register(request.Abort, false)) { try { return (HttpWebResponse)await request.GetResponseAsync(); } catch (Exception) { ct.ThrowIfCancellationRequested(); throw; } } } - Joseph Lennox
1
@JosephLennox,你可以这样做,但是在客户端代码中,当取消请求时,你将失去对WebException的访问权限,它目前作为Exception.InnerException可用。 - noseratio - open to work
1
如果这段代码到达了 ct.ThrowIfCancellationRequested();,我认为变量 response 应该被关闭。 - zirbel
@zirbel,你是对的, ThrowIfCancellationRequested 在这里和成功完成的 HttpWebRequest.GetResponseAsync 竞争。我移除了前者,将其留给调用者来处理这个竞争条件(当 Extensions.GetResponseAsync 可能同时完成时,该条件仍然存在)。 - noseratio - open to work

10
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())
{
    . . .
}

will become:

var request = (HttpWebRequest)WebRequest.Create(url);

using (WebResponse response = await request.GetResponseAsync().WithCancellation(CancellationToken.None, request.Abort, true))
{
    . . .
}

请查看示例:http://pastebin.com/KauKE0rW

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接