可取消的异步 Web 请求

Cancellable Async Web Request

我正在尝试创建一个通用方法来取消异步 Web 请求以及与之关联的任何其他操作。我发现了另一个与此相关的问题,例如 this.

我已经编写了一个帮助程序 class 来完成这项工作。我在下面展示:

     public static class Helpers
    {
        public static async Task<string> GetJson(string url,
            CancellationTokenSource cancellationTokenSource,
            bool useSynchronizationContext = true)
        {
            try
            {
                var request = (HttpWebRequest)WebRequest.Create(url);
                string jsonStringResult;
                using (WebResponse response = await request.GetResponseAsync()
                    .WithCancellation(cancellationTokenSource.Token,
                    request.Abort, useSynchronizationContext))
                {
                    Stream dataStream = response.GetResponseStream();
                    StreamReader reader = new StreamReader(dataStream);
                    jsonStringResult = await reader.ReadToEndAsync();
                    reader.Close();
                    dataStream.Close();
                }

                cancellationTokenSource.Token.ThrowIfCancellationRequested();

                return jsonStringResult;
            }
            catch (Exception ex) when (ex is OperationCanceledException
                || ex is TaskCanceledException)
            {

            }
            catch (Exception ex) when (ex is WebException
                && ((WebException)ex).Status == WebExceptionStatus.RequestCanceled)
            {

            }
            catch (Exception ex)
            {
                //Any other exception
            }
            finally
            {
                cancellationTokenSource.Dispose();
            }
            return default;
        }


        public static async Task<T> WithCancellation<T>(this Task<T> task,
            CancellationToken cancellationToken, Action action,
            bool useSynchronizationContext)
        {
            using (cancellationToken.Register(action, useSynchronizationContext))
            {
                return await task;
            }
        }
    }

注意行

cancellationTokenSource.Token.ThrowIfCancellationRequested();

就在返回 JSON 字符串之前。

当操作被取消并且执行流程在线时

StreamReader reader = new StreamReader(dataStream);

例如,下面的所有行(reader.Close() 等)都将被执行,并且在执行 ThrowIfCancelationRequested() 时将抛出异常 - 是否正确?我错过了什么吗?

如果是这样,有没有办法一次性全部取消?

感谢大家的回复,

在提供了答案和所有真正有用的评论后,我更新了 implementation.I 使用的 HttpClient 和 link 中的扩展方法,使一项实际上不能被取消的任务表现得像一个可以取消的任务- 实际执行了 readAsStringAsync,因为它不能接受取消令牌。

public static class Helpers
    {
        public static async Task<string> GetJson(string url,CancellationToken cancellationToken)
        {
            try
            {
                string jsonStringResult;

                using (var client = new HttpClient())
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    using (var response = await client.GetAsync(url, cancellationToken))
                    {
                        jsonStringResult = await response.Content.ReadAsStringAsync().WithCancellation(cancellationToken);
                    }
                }

                return jsonStringResult;
            }
            catch (Exception ex) when (ex is OperationCanceledException)
            {

            }
            catch (Exception ex) when (ex is WebException exception && exception.Status == WebExceptionStatus.RequestCanceled)
            {

            }
            catch (Exception ex)
            {
                //LogException(ex);
            }
            return default;
        }

        public static Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
        {
            return task.IsCompleted 
                ? task: task.ContinueWith(completedTask => completedTask.GetAwaiter().GetResult(),cancellationToken,TaskContinuationOptions.ExecuteSynchronously,TaskScheduler.Default);
        }
    }

all lines below (reader.Close() etc.) will be executed and the exception will be thrown when ThrowIfCancelationRequested() is executed - is that correct? Am I missing something?

If so, is there a way to cancel everything at once?

首先,您要取消的操作必须明确支持被取消。因此,您必须尽力使用代码以某种方式以某种方式将 CancellationToken 作为参数来执行某些操作。如果不可能,那么你就没有其他机会了。

这就是为什么这个概念被称为 合作取消。因为几乎总是双方都应该知道发生了取消。客户端应该知道代码实际上被取消了,客户端仅仅知道取消请求是不够的。对于被调用者,重要的是要了解请求取消以正确完成自身的事实。

关于在执行streamreaderClose方法时检查操作是否被取消。如果要避免内存泄漏,无论是否取消操作,都必须始终调用清理方法。当然,我建议您使用 using 语句,它会自动进行清理。

顺便说一句,为了使某些功能可取消,您不必在执行每一行之前检查是否请求取消。您只需要在执行一些 long-运行 操作之前和之后检查是否请求取消。如果 long-运行 操作支持通过取消令牌 属性.

取消,则传递取消令牌

此外,您必须查看副作用。如果您已经招致副作用,您的方法不准备在退出时恢复,这会使您处于不一致状态,请不要取消。

一些通用的代码块可能是这样的:

if(ct.IsCancellationRequested)
{
    break; // or throw
}

await DoSomething(ct);

if (ct.IsCancellationRequested)
{
    // if there is no side-effect
    return; // or throw

    // or, we already did something in `DoSomething` method
    // do some rollback
}

作为解决方案,您可以使用一些不同的对象,如 HttpClientWebRequest 来执行异步、可等待和可取消的 Web 请求。您可以查看 了解实施细节。