Отменяемый асинхронный веб-запрос - PullRequest
2 голосов
/ 10 ноября 2019

Я пытаюсь создать универсальный метод, который отменяет веб-запрос Async и любые дополнительные операции, связанные с ним. Я нашел другой вопрос относительно этого, такой как this .

И я написал вспомогательный класс, который будет делать именно это. Я представляю это ниже:

     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() - это правильно? Я что-то упустил?

Если так, есть ли способ отменить все сразу?

Спасибо всем за ответ,

После предоставленного ответа и всех действительно полезных комментариев я обновил реализацию. Я использовал HttpClient и метод расширения в ссылке для выполнения задачи, которая на самом деле не может быть решена. отменено, чтобы вести себя как тот, который может - 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);
        }
    }

1 Ответ

1 голос
/ 10 ноября 2019

все строки ниже (reader.Close () и т. Д.) Будут выполнены, и будет вызвано исключение при выполнении ThrowIfCancelationRequested () - это правильно? Я что-то упустил?

Если так, есть ли способ отменить все сразу?

Прежде всего, операция, которую вы хотите отменить, имеет значение явно поддержка отменяется. Итак, вы должны приложить все усилия, чтобы использовать код для выполнения некоторой операции таким образом, который каким-то образом принимает CancellationToken в качестве аргумента. Если это невозможно, у вас нет другого шанса.

Вот почему эта концепция называется Совместное аннулирование . Потому что почти всегда обе стороны должны знать, что отмена произошла. Клиентская сторона должна знать, что код был фактически отменен, клиенту недостаточно знать, что аннулирование было запрошено. Для вызываемой стороны важно знать о том, что отмена была запрошена, чтобы правильно завершить себя.

Относительно проверки отмены операции при выполнении Close метода stream и reader. Вы должны вызывать методы очистки всегда, независимо от того, отменена операция или нет, если вы хотите избежать утечек памяти. Конечно, я предлагаю вам использовать оператор using, который автоматически выполнит эту очистку.

И, между прочим, для того, чтобы сделать какую-либо функцию отменяемой, вам не нужно проверять, запрашивается ли отмена перед выполнением каждой строки. Вам просто нужно проверить, отменен ли запрос до и после выполнения какой-либо длительной операции. И передайте токен отмены, если эти длительные операции поддерживают отмену с помощью свойства токена отмены.

Кроме того, вам нужно посмотреть побочных эффектов . Не отменяйте, если вы уже столкнулись с побочными эффектами, которые ваш метод не готов отменить, если вы окажетесь в несогласованном состоянии.

Некоторые общие кодовые блоки могут быть такими:

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
}

В качестве решения вы можете использовать несколько различных объектов, таких как HttpClient или WebRequest для выполнения асинхронного, ожидаемого и отменяемого веба. запрос. Вы можете посмотреть эту ссылку для подробностей реализации.

...