GetResponseAsync不接受cancellationToken

Jim*_*Jim 14 c# httpwebrequest httpwebresponse async-await cancellation-token

似乎GetResponseAsync在Async/Await中不接受cancellationToken.所以问题是我如何取消以下程序,只要我需要从响应中收集Cookie:

 using (HttpWebResponse response = (HttpWebResponse) await request.GetResponseAsync())
 {
    cookies.Add(response.Cookies);
 }
Run Code Online (Sandbox Code Playgroud)

实现上述目标的替代代码也是受欢迎的.

nos*_*tio 30

这样的东西应该工作(未经测试):

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

从理论上讲,如果请求在取消ctrequest.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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*ian 8

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在您可以在任何可取消的异步方法上使用您的取消令牌。例如 WebRequest.GetResponseAsync:

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

using (var response = await request.GetResponseAsync())
{
    . . .
}
Run Code Online (Sandbox Code Playgroud)

会变成:

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

using (WebResponse response = await request.GetResponseAsync().WithCancellation(CancellationToken.None, request.Abort, true))
{
    . . .
}
Run Code Online (Sandbox Code Playgroud)

参见示例http://pastebin.com/KauKE0rW