使用 CancellationToken 和 Ctrl+C 取消多个 HttpClient 调用
Cancel multiple HttpClient calls using CancellationToken and Ctrl+C
我正在尝试在命令行应用程序、完整框架、.net 4.7.1、C# 中使用 CancellationTokens
取消通过共享 HttpClient
发出的多个异步 Web 请求 (GET) 7.3,对比 2017.
我的示例运行几个并行任务,每个任务都使用异步 GetAsync()
和 ReadAsStringAsync()
通过 Http 不断下载一些数据,但我得到相同的结果,例如 ReadAsByteArrayAsync()
.
通过连接到 Console.CancelKeyPress
并取消我的 CancellationTokenSource
来完成终止。
虽然出于某种原因这看起来很简单,但我无法理解它并想出一个产生可靠结果的解决方案。有时一切都会按预期关闭,即所有任务都已完成(取消),但更常见的是没有关闭只是看似挂起。 运行 没有调试器 (with/without DEBUG) 终止应用程序但不是我预期的方式。更少的任务意味着更有可能完全关闭。
在处于 "hang" 状态时暂停调试中的所有线程似乎表明某些线程卡在了 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 . . .
好吧,通过 Whosebug 的橡皮擦又成功了。
如果未通过 args.Cancel = true
取消,Ctrl+C
将终止应用程序,因此它与 HttpClient
或 CancellationToken
.
的关系绝对为零
解决方法很简单:
Console.CancelKeyPress += (sender, args) =>
{
args.Cancel = true;
Console.WriteLine("Stopped by user");
cancellationTokenSource.Cancel(true);
};
我正在尝试在命令行应用程序、完整框架、.net 4.7.1、C# 中使用 CancellationTokens
取消通过共享 HttpClient
发出的多个异步 Web 请求 (GET) 7.3,对比 2017.
我的示例运行几个并行任务,每个任务都使用异步 GetAsync()
和 ReadAsStringAsync()
通过 Http 不断下载一些数据,但我得到相同的结果,例如 ReadAsByteArrayAsync()
.
通过连接到 Console.CancelKeyPress
并取消我的 CancellationTokenSource
来完成终止。
虽然出于某种原因这看起来很简单,但我无法理解它并想出一个产生可靠结果的解决方案。有时一切都会按预期关闭,即所有任务都已完成(取消),但更常见的是没有关闭只是看似挂起。 运行 没有调试器 (with/without DEBUG) 终止应用程序但不是我预期的方式。更少的任务意味着更有可能完全关闭。
在处于 "hang" 状态时暂停调试中的所有线程似乎表明某些线程卡在了 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 . . .
好吧,通过 Whosebug 的橡皮擦又成功了。
如果未通过args.Cancel = true
取消,Ctrl+C
将终止应用程序,因此它与 HttpClient
或 CancellationToken
.
解决方法很简单:
Console.CancelKeyPress += (sender, args) =>
{
args.Cancel = true;
Console.WriteLine("Stopped by user");
cancellationTokenSource.Cancel(true);
};