我为每个用户创建了一个循环,它使用Task.Factory.StartNew()创建了一个轻量级任务。循环中的任务做了一个轻量级的工作,并睡眠了几秒钟。
如果有几千或几百万用户同时工作,这样的代码还能用吗?管理这么多线程会不会对服务器造成很大的负载?让一个线程为所有用户做这项工作是否更好?
这就是目前代码中的情况。
Task.Factory.StartNew(() =>
{
// begin loop
// synchronous web call
// process and update result in DB
// exit condition
// sleep for few minutes
// end loop
}
你可以有几百万个长期运行的任务,但你不能有几百万个长期运行的线程(除非你拥有一台有TB内存的机器,因为每个线程都分配了 1 MB). 有这么多任务的方法是让它们成为async。而不是让它们与 Thread.Sleep
你可以让他们 等待 异步a Task.Delay
. 下面是一个例子。
var cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
Task[] tasks = Enumerable.Range(1, 1_000_000).Select(index => Task.Run(async () =>
{
await Task.Delay(index, ct); // Initial delay to spread things out
while (true)
{
var webResult = await WebCallAsync(index, ct); // asynchronous web call
await DbUpdateAsync(webResult, ct); // update result in DB
await Task.Delay(1000 * 60 * 10, ct); // do nothing for 10 minutes
}
})).ToArray();
Task.WaitAll(tasks);
目的是... CancellationTokenSource
是用于在任何时候通过调用 cts.Cancel()
. 结合 Task.Delay
但取消会产生意想不到的开销,因为取消会通过 OperationCanceledException
异常,而100万个异常会给.NET基础架构造成相当大的压力。在我的PC中,开销大约是50秒100%的CPU消耗。如果你确实喜欢使用 CancellationToken
s,一个变通的办法是使用一个替代的 Task.Delay
不抛出异常。这里是这个想法的一个实现。
/// <summary>Returns a <see cref="Task"/> that will complete with a result of true
/// if the specified number of milliseconds elapsed successfully, or false
/// if the cancellation token is canceled.</summary>
private static async Task<bool> NonThrowingDelay(int millisecondsDelay,
CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested) return false;
if (millisecondsDelay == 0) return true;
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(() => tcs.TrySetResult(false)))
using (new Timer(_ => tcs.TrySetResult(true), null, millisecondsDelay, Timeout.Infinite))
return await tcs.Task.ConfigureAwait(false);
}
下面是如何使用 NonThrowingDelay
创建100万个可以立即取消的任务(几乎)的方法。
var cts = new CancellationTokenSource();
CancellationToken ct = cts.Token;
Task[] tasks = Enumerable.Range(1, 1_000_000).Select(index => Task.Run(async () =>
{
if (!await NonThrowingDelay(index, ct)) return; // Initial delay
while (true)
{
var webResult = await WebCallAsync(index, ct); // asynchronous web call
await DbUpdateAsync(webResult, ct); // update result in DB
if (!await NonThrowingDelay(1000 * 60 * 10, ct)) break; // 10 minutes
}
})).ToArray();
Task.WaitAll(tasks);