【问题标题】:Cancel multiple HttpClient calls using CancellationToken and Ctrl+C使用 CancellationToken 和 Ctrl+C 取消多个 HttpClient 调用
【发布时间】:2018-05-29 16:29:24
【问题描述】:

我正在尝试在命令行应用程序、完整框架、.net 4.7.1、C# 7.3、VS 2017 中使用 CancellationTokens 取消通过共享 HttpClient 发出的多个异步 Web 请求 (GET)。

我的示例运行了几个并行任务,每个任务不断通过 Http 下载一些数据,使用异步 GetAsync()ReadAsStringAsync(),但我使用 ReadAsByteArrayAsync() 得到相同的结果。

通过连接到Console.CancelKeyPress 并取消我的CancellationTokenSource 来完成终止。

尽管由于某种原因这看起来很简单,但我无法理解它并想出一个产生可靠结果的解决方案。有时一切都按预期关闭,即所有任务都完成(取消),但更常见的是,不关闭似乎只是挂起。在没有调试器的情况下运行(有/无 DEBUG)会终止应用程序,但不会以我预期的方式终止。更少的任务意味着更有可能彻底关闭。

当处于“挂起”状态时暂停调试中的所有线程似乎表明一些线程卡在GetAsync(),但很难确切地看到发生了什么。

实际上,应用程序如何退出并不重要,但我想了解这一点,并能够始终如一地产生干净且受控的关闭,在我看来,使用这种结构是可能的,但我很可能错过了一些详情。

using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace HttpClientAsyncTest
{
    class Program
    {
        static async Task<int> Main()
        {
            using (var cancellationTokenSource = new CancellationTokenSource())
            {
                Console.CancelKeyPress += (sender, a) =>
                {
                    Console.WriteLine("Stopped by user");
                    cancellationTokenSource.Cancel();
                };

                var task = RunAsync(cancellationTokenSource);
                Console.WriteLine("Running - Press CTRL+C to stop");
                await task;
            }

            Console.WriteLine("All done, exiting");

            return 0;
        }

        private static async Task RunAsync(CancellationTokenSource cts)
        {
            using (var client = new HttpClient())
            {
                try
                {
                    var tasks = Enumerable.Range(1, 10).Select(id => ProcessBatchAsync(client, id, cts.Token));

                    await Task.WhenAll(tasks);
                }
                catch (OperationCanceledException)
                {
                    Console.WriteLine("Operation cancelled somehow");
                }
            }
        }

        private static async Task ProcessBatchAsync(HttpClient client, int id, CancellationToken cancellationToken)
        {
            while (true)
                await ProcessNextBatchAsync(client, id, cancellationToken);
        }

        private static async Task ProcessNextBatchAsync(HttpClient client, int id, CancellationToken cancellationToken)
        {
            using (var response = await client.GetAsync("http://some.payload.to.request", cancellationToken))
            {
                if (response.StatusCode == HttpStatusCode.NotFound)
                    return;

                var data = await response.Content.ReadAsStringAsync();
                Console.WriteLine($"Id: {id} downloaded {data.Length} chars");
            }
        }
    }
}

我希望在按下 Ctrl-C 时看到类似的内容:

Running - Press CTRL+C to stop
Id: 6 downloaded 475357 chars
Id: 2 downloaded 475141 chars
Id: 3 downloaded 474927 chars
Id: 5 downloaded 474457 chars
Id: 8 downloaded 474524 chars
Id: 4 downloaded 474643 chars
Id: 7 downloaded 475133 chars
Id: 9 downloaded 475316 chars
Stopped by user
Operation cancelled somehow
All done, exiting
Press any key to continue . . .

但大多数情况下我会得到类似的东西:

Running - Press CTRL+C to stop
Id: 3 downloaded 474927 chars
Id: 8 downloaded 474524 chars
Id: 5 downloaded 474457 chars
Id: 9 downloaded 475316 chars
Id: 6 downloaded 475357 chars
Id: 7 downloaded 474952 chars
Id: 2 downloaded 475513 chars
Stopped by user
Id: 4 downloaded 475457 chars
Id: 7 downloaded 475133 chars
^CPress any key to continue . . .

【问题讨论】:

  • 您没有取消任务,您必须在令牌源上调用Cancel,并且当您在await执行任务时,执行被暂停并且您没有任何方式接收执行取消的信号。当您在控制台应用程序上按ctrl+c 时,它会强制退出,因此您的代码无法按预期工作。此外,取消的异步任务并不能确保在对 Cancel 的调用返回之前终止,它可能需要一些时间才能发生。
  • 我正在通过Console.CancelKeyPress 取消源,但正如您所指出的,我并没有取消控制台终止应用程序。这样做可以让一切按预期工作,因为我使用Task.WhenAll,所以等待干净关闭。感谢ctrl+c 上的指针,我对此一无所知。或者很久以前就忘记了。

标签: c# .net dotnet-httpclient


【解决方案1】:

好吧,通过 StackOverflow 让自己躲起来又奏效了。

Ctrl+C 如果没有通过args.Cancel = true 取消,则终止应用程序,因此它与HttpClientCancellationToken 的关系绝对为零。

解决方法很简单:

Console.CancelKeyPress += (sender, args) =>
{
    args.Cancel = true;
    Console.WriteLine("Stopped by user");
    cancellationTokenSource.Cancel(true);
};

【讨论】:

    猜你喜欢
    • 2016-03-08
    • 2020-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多