下面的代码应该(至少在我看来)创建100个Tasks
,它们都在并行等待(这就是关于并发的点,对吧:D?)并且几乎同时完成。我想每个Task.Delay
内部都会创建一个Timer
对象。
public static async Task MainAsync() {
var tasks = new List<Task>();
for (var i = 0; i < 100; i++) {
Func<Task> func = async () => {
await Task.Delay(1000);
Console.WriteLine("Instant");
};
tasks.Add(func());
}
await Task.WhenAll(tasks);
}
public static void Main(string[] args) {
MainAsync().Wait();
}
但是!当我在Mono上运行时,我得到了非常奇怪的行为:
Tasks
没有同时完成,有很大的延迟(可能大约500-600毫秒)加载程序集:/Users/xxxxx/Programming/xxxxx/xxxxxxxxxx/bin/Release/xxxxx.exe
线程开始:#2
线程开始:#3
线程开始:#4
线程开始:#5
线程开始:#6
线程开始:#7
线程完成:#3&lt; - 显然延迟1000ms完成了?
线程完成:#2&lt; - 显然延迟了1000ms?
线程开始:#8
线程开始:#9
线程开始:#10
线程开始:#11
线程开始:#12
线程开始:#13
......你明白了。
这实际上是一个错误吗?或者我使用错误的库?
[编辑] 我使用Timer测试了一个自定义睡眠方法:
public static async Task MainAsync() {
Console.WriteLine("Started");
var tasks = new List<Task>();
for (var i = 0; i < 100; i++) {
Func<Task> func = async () => {
await SleepFast(1000);
Console.WriteLine("Instant");
};
tasks.Add(func());
}
await Task.WhenAll(tasks);
Console.WriteLine("Ready");
}
public static Task SleepFast(int amount) {
var source = new TaskCompletionSource<object>();
new Timer(state => {
var oldSrc = (TaskCompletionSource<object>)state;
oldSrc.SetResult(null);
}, source, amount, 0);
return source.Task;
}
这一次,所有任务都是即时完成的。所以,我认为这是一个非常糟糕的实现或错误。
[EDIT2]
仅供参考:我现在使用Windows 8.1在.NET上测试了原始代码(使用Task.Delay
)并按预期运行(1000 Tasks
,并行等待1秒并完成)。
所以答案是:Mono的impl。 (某些)方法并不完美。通常Task.Delay
不会启动一个线程,甚至很多线程都不应该创建多个线程。
答案 0 :(得分:3)
Task
库设计用于管理阻止任务而不会阻塞整个工作流(任务异步,混淆地称为Microsoft的“任务并行”),不用于执行大块并发计算(并行执行)。
任务库使用调度程序并将作业排队等待执行。当作业运行时,它们将在线程池线程上执行,并且这些线程的数量非常有限。有扩展线程数的逻辑,但除非你有数百个CPU核心,否则它将保持较低的数量。
所以为了回答这个问题,你的一些任务排队等待来自池的线程,而其他延迟的任务已由调度程序发出。
调度程序和线程池逻辑可以在运行时进行更改,但如果您尝试快速完成大量计算Task
不适合该作业。如果您想处理大量缓慢的资源(如磁盘,数据库或Internet资源),Task
可能有助于保持应用程序的响应。
如果您只是想了解Task
请尝试以下内容:
答案 1 :(得分:1)
在.NET Framework桌面上。
简而言之,这个特殊的VM线程会定期检查定时器队列并在线程池队列上运行定时器的委托。 Task.Delay不会创建新的线程,但仍然可能很重,并且对执行顺序或准确的截止日期没有保证。根据我的理解,传递取消Task.Delay
可能最终只是从集合中删除项目,没有线程池工作排队。
Task.Delay通过创建新的DelayPromise安排为System.Threading.Timer。所有计时器都存储在TimerQueue的AppDomain单例中。 Native VM timer用于回调.NET以检查是否需要从队列中触发任何计时器。计划代表通过ThreadPool.UnsafeQueueUserWorkItem计划执行。