![](/img/trans.png)
[英]CancellationTokenSource.Cancel Method Can be ignored by the task cancellation is requested
[英]CancellationTokenSource.Cancel() very slow to trigger registered cancellation delegates
我在 blazor 服务器应用程序中遇到 HTTP 请求取消问题。 我有一个 web 应用程序,它使用带有自定义瓷砖覆盖的谷歌地图。 当用户缩放时,map 将向我的服务器请求 256x256 瓦片。 当用户放大时,Google 地图会适当地取消任何不再需要的待处理 HTTP 请求。 但是,如果用户快速移动,则会发生很多取消。 由于所有取消,服务器中的某些内容被延迟。 为了尝试调试它,我制作了一个简单的控制台应用程序,它使用 Task.Delay 获取取消令牌。 由于执行的任务相对较少(逻辑核心数为 2 倍)),事情按预期工作。 随着任务数量的增加,延迟变得极端。 我制作了一个 GitHub 存储库,在以下位置演示了所有这些: https://github.com/TNT0305/TestWait
在 main 顶部附近,有一些配置参数:
在我的机器上,将inducedDelay 设置为true,我收到512 次任务调用。 100 毫秒后,我在取消令牌源上调用 Cancel()。 对 Cancel() 的调用需要 18.4 秒才能返回。
我想我一定错过了什么。 有任何想法吗?
问题不一定是 for.Cancel() 返回需要多长时间。 它与启动 .Cancel() 调用后在异步任务中检测到取消需要多长时间有关。
这是 GitHub 存储库中的Program.cs
:
using System.Text;
namespace testWait
{
/// <summary>
/// Class to record information on timing of tasks
/// </summary>
class Event
{
public int Id { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public bool CancelledBeforeStart { get; set; } = false;
public DateTime? CancelTriggerTime { get; set; }
public DateTime? CancelExceptionTime { get; set; }
// Properties to make sense of the recorded times
public double TotalDuration { get => (EndTime - StartTime).TotalSeconds; }
public double TriggeredAfter { get => CancelTriggerTime != null ? (CancelTriggerTime.Value - StartTime).TotalSeconds : -1.0; }
public double ExceptionAfter { get => CancelExceptionTime != null ? (CancelExceptionTime.Value - StartTime).TotalSeconds : -1.0; }
public override string ToString() => $"{Id},{StartTime},{TotalDuration},{TriggeredAfter},{ExceptionAfter},{CancelledBeforeStart}";
public string ReportCancel(DateTime cancelTime)
{
var t = CancelTriggerTime != null ? (CancelTriggerTime.Value - cancelTime).TotalSeconds : -1.0;
return $"{Id} cancel delegate called {t}s after cts.Cancel() was called";
}
}
internal class Program
{
static async Task Main(string[] args)
{
//////////////////////////////////////////////////////
// RUN CONFIGURATION
// set induceIssue to true to observe excessive delay. Set to false to observe expected behaviors
bool induceIssue = true;
int taskDelayMs = 30000; // Task.DelayAsync for 20 seconds
//////////////////////////////////////////////////////
// Hold results from all calls. Outer Main has Id=-1
List<Event> Events = new List<Event>();
int taskCount = Environment.ProcessorCount << 1; // twice as many tasks as logical cores
if (induceIssue) taskCount = Environment.ProcessorCount << 5; // 2^5 as may tasks as cores
Console.WriteLine($"Starting {taskCount} tasks on {Environment.ProcessorCount} Logical Cores");
DateTime start = DateTime.Now;
DateTime end = start;
var te = new Event
{
Id = -1,
StartTime = start,
EndTime = end
};
// Add the event representing the entire "Main"
Events.Add(te);
using var cts = new CancellationTokenSource();
// record the time when we detected the token was triggered
cts.Token.Register(() => end = DateTime.Now);
// create an array of cancellable tasks
var tasks = (from i in Enumerable.Range(0, taskCount) select DoSomething(i, taskDelayMs, cts.Token)).ToArray();
// try with Task.Run to see if it makes a difference (it does now))
//var tasks = (from i in Enumerable.Range(0, taskCount) select Task.Run(async () => await DoSomething(i, taskDelayMs, cts.Token), cts.Token)).ToArray();
// CancelAfter is what we want, but let's call cancel, explicitly, to observe delays
//cts.CancelAfter(200);
// wait 100ms to trigger the cancellation so that we have a chance to enter into the Task.Delay(...) calls
DateTime cancelStart = DateTime.MinValue;
var triggerTask = Task.Run(async () =>
{
await Task.Delay(100);
cancelStart = DateTime.Now;
var triggerTime = (cancelStart - start).TotalSeconds;
Console.WriteLine($"Cancelling work after {triggerTime}s");
cts.Cancel();
DateTime cancelEnd = DateTime.Now;
triggerTime = (cancelEnd - start).TotalSeconds;
var cancelDuration = (cancelEnd - cancelStart).TotalSeconds;
// report time at which the token source finished the call to Cancel() (observe long delay)
Console.WriteLine($"After calling cancel: {triggerTime}s (ctr..Cancel() duration: {cancelDuration})");
te.CancelTriggerTime = DateTime.Now;
});
try
{
// use wait instead of when to pass the token into the WaitAll rather than relying on "DoSomething"
//Task.WaitAll(tasks, cts.Token);
Events.AddRange(await Task.WhenAll(tasks));
}
catch (OperationCanceledException oce)
{
// records the time when the exception threw the OperationCancelledException (if it is thrown)
te.CancelExceptionTime = DateTime.Now;
Console.WriteLine("Main Task Cancelled Exception");
}
te.EndTime = DateTime.Now;
var duration = (DateTime.Now - start).TotalSeconds;
var cancelAfter = (end - start).TotalSeconds;
await triggerTask;
//wait for them all to _actually_ finish
//Events.AddRange(await Task.WhenAll(tasks));
#region Build results String
var sb = new StringBuilder();
// sort the events by when the cancellation token was triggered
foreach (var e in Events.OrderBy(e => e.TriggeredAfter).ToList())
{
//sb.AppendLine(e.ToString());
sb.AppendLine(e.ReportCancel(cancelStart));
}
#endregion
// Write out all of the results
Console.Write(sb.ToString());
Console.WriteLine($"MainTask, taskDuration: {duration}, cancelAfter: {cancelAfter}");
Console.WriteLine("Done processing. Press any key");
Console.ReadKey();
}
static async Task<Event> DoSomething(int i, int delayMs, CancellationToken token)
{
Event e = new();
//lock (Events) Events.Add(e);
try
{
e.Id = i;
//lock(log) log.AppendLine($"{i} started");
e.StartTime = DateTime.Now;
e.EndTime = e.StartTime;
// record the time when we detected the token was triggered
token.Register(() => e.CancelTriggerTime = DateTime.Now);
if (token.IsCancellationRequested)
{
e.CancelledBeforeStart = true;
return e;
}
try
{
await Task.Delay(delayMs, token);
}
catch (TaskCanceledException tce)
{
e.CancelExceptionTime = DateTime.Now;
}
e.EndTime = DateTime.Now;
//await Task.Delay(20);
return e;
}
finally
{
e.EndTime = DateTime.Now;
}
}
}
}
呸。 在调试模式下运行时,Visual Studio 中的异常报告缓慢。 当我在发布模式下运行它时,它会按预期运行。 我最初的问题涉及的组件比我的示例使用的要多,并且下游代码存在我已经纠正的问题。
要点:如果您遇到性能问题,请在调试器之外尝试(在 VS 中使用 Ctrl+F5 而不是 F5)。 >.<
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.