我需要编写一个接收事件的组件(事件具有唯一的ID)。每个活动都要求我发出请求。该事件指定一个超时期限,等待来自请求的响应。
如果响应在计时器触发之前发生,那很好,我取消了计时器。 如果计时器首先触发,那么请求超时,我想继续前进。
此超时期限在事件中指定,因此它不是常量。 预期的超时时间在30秒到5分钟的范围内。
我可以看到实现这两种方法。
选项1似乎是最简单的解决方案,但我担心创建这么多计时器可能不是一个好主意,因为计时器可能太贵了。在创建大量计时器时是否存在任何陷阱?我怀疑在后台,定时器实现可能实际上是选项2的有效实现。如果这个选项是个好主意,我应该使用哪个定时器? System.Timers.Timer或System.Threading.Timer。
选项2似乎更有效,与选项1相比可能不是一种有效的解决方案。
更新
我期望的最大定时器数量在10000的范围内,但更有可能在100的范围内。另外,正常情况是在触发前取消定时器。
更新2
我使用System.Threading.Timer
和System.Timers.Timer
的10K实例运行测试,密切关注线程数和内存。 System.Threading.Timer
与内存使用情况判断相比System.Timers.Timer
似乎“更轻”,并且两个定时器都没有创建过多的线程(即 - 线程池正常工作)。所以我决定继续使用System.Threading.Timer
。
答案 0 :(得分:5)
我在嵌入式系统(纯c)中做了很多,我无法刻录大量资源(例如,4k的RAM是系统内存)。这是一种已成功使用的方法:
定时器关闭时会发生什么情况取决于应用程序。它可能是状态机运行。它可能是一个被调用的函数。它可能是一个枚举,告诉执行代码如何处理参数,并将其发送给“Create Timer”调用。定时器结构中的信息是设计上下文中的必要信息。 “滴答计数”是秘诀。
我们还创建了这个返回定时器的“ID”(通常是从池中抽取的定时器结构的地址),因此可以取消它或在其上获得状态。
便利功能将“秒”转换为“滴答”,因此创建定时器的API始终以“秒”或“毫秒”表示。
您可以将“tick”间隔设置为合理的粒度权衡值。
我已经在C ++,C#,objective-C中完成了其他实现,一般方法几乎没有变化。它是一种非常通用的定时器子系统设计/架构。你只需要一些东西来创造基本的“滴答声”。
我甚至用一个紧密的“主”循环和高精度内部计时器的秒表做了一次,当我没有计时器时创建我自己的“模拟”刻度。我不推荐这种方法;我在一个直接的控制台应用程序中模拟硬件,并且无法访问系统计时器,所以这是一个极端的情况。
在一台现代处理器上迭代数百个定时器的列表每秒10次并不是什么大不了的事。您可以通过插入带有“delta seconds”的项目并按排序顺序将它们放入列表中来克服此问题。这样您只需检查列表前面的那些。这会让你超越扩展问题,至少在迭代列表方面。
这有用吗?
答案 1 :(得分:4)
第一个选项根本就不会扩展,如果你有很多并发超时,你将需要做其他的事情。 (如果您不知道自己有多少就足够成为一个问题,请随时尝试使用计时器来确定您是否确实遇到问题。)
那就是说,你的第二个选择需要一些调整。只需创建一个单个计时器,并将其间隔(每次触发)设置为当前时间和“下一个”超时时间之间的时间跨度,而不是在新线程中使用紧密循环。 / p>
答案 2 :(得分:3)
你应该以最简单的方式做到这一点。如果您担心性能,则应通过分析器运行应用程序并确定瓶颈。您可能会非常惊讶地发现它是您最不期望的代码,并且您已经无缘无故地优化了代码。我总是编写最简单的代码,因为这是最简单的。见PrematureOptimization
我不明白为什么会有大量计时器出现任何陷阱。我们在谈论十几个,或100个,还是10,000个?如果它非常高,你可能会遇到问题。您可以编写一个快速测试来验证这一点。
至于哪些Timer类使用:我不想窃取任何可能做更多研究的人的答案:查看this answer to that question`
答案 3 :(得分:1)
让我提出一个不同的架构:对于每个事件,只需创建一个新的Task并发送请求并等待 1 以获得响应。
〜{1000>任务应该可以正常扩展,如this early demo所示。我怀疑〜10000个任务仍然可以扩展,但我自己没有测试过。
1 考虑通过在Task.Delay(而不仅仅是Thread.Sleep)上附加延续来实现等待,以避免订阅不足。
答案 4 :(得分:1)
我认为Task.Delay
是一个非常好的选择。这是测试代码,用于测量在不同的延迟时间内可以执行多少个并发任务。该代码还在计算错误统计信息,以提高等待时间的准确性。
static async Task Wait(int delay, double[] errors, int index)
{
var sw = new Stopwatch();
sw.Start();
await Task.Delay(delay);
sw.Stop();
errors[index] = Math.Abs(sw.ElapsedMilliseconds - delay);
}
static void Main(string[] args)
{
var trial = 100000;
var minDelay = 1000;
var maxDelay = 5000;
var errors = new double[trial];
var tasks = new Task[trial];
var rand = new Random();
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < trial; i++)
{
var delay = rand.Next(minDelay, maxDelay);
tasks[i] = Wait(delay, errors, i);
}
sw.Stop();
Console.WriteLine($"{trial} tasks started in {sw.ElapsedMilliseconds} milliseconds.");
Task.WaitAll(tasks);
Console.WriteLine($"Avg Error: {errors.Average()}");
Console.WriteLine($"Min Error: {errors.Min()}");
Console.WriteLine($"Max Error: {errors.Max()}");
Console.ReadLine();
}
您可以更改参数以查看不同的结果。以下是几毫秒的结果:
100000个任务在9353毫秒内启动。
平均错误:9.10898
最小错误:0
最大错误:110