C# 继续任务上的Task.WaitAll()仅延迟原始任务的执行?
背景: 我有一个控制台应用程序,它可以创建C# 继续任务上的Task.WaitAll()仅延迟原始任务的执行?,c#,.net,multithreading,task-parallel-library,C#,.net,Multithreading,Task Parallel Library,背景: 我有一个控制台应用程序,它可以创建任务来处理数据库中的数据(我们称之为Level1任务)。每个任务都会再次创建自己的任务,以处理分配给它的数据的每个部分(级别2任务) 每个Level2任务都有一个与其关联的延续任务,代码用于在继续之前对延续任务执行WaitAll 我在.net4.0(无async/wait) 问题: 但这产生了一个问题——结果是,如果采用这种方式,则在计划所有可用的级别1任务之前,没有一个级别2任务启动。这在任何方面都不是最优的 问题: 通过将代码更改为等待原始的Leve
任务来处理数据库中的数据(我们称之为Level1任务)。每个任务都会再次创建自己的任务,以处理分配给它的数据的每个部分(级别2任务)
每个Level2任务都有一个与其关联的延续任务,代码用于在继续之前对延续任务执行WaitAll
我在.net4.0
(无async
/wait
)
问题:
但这产生了一个问题——结果是,如果采用这种方式,则在计划所有可用的级别1任务之前,没有一个级别2任务启动。这在任何方面都不是最优的
问题:
通过将代码更改为等待原始的Level2任务及其延续任务,似乎可以解决这一问题。然而,我不完全确定为什么会这样
你有什么想法吗
我能想到的唯一一件事是——既然延续任务还没有开始,就没有必要等待它完成。但即使是这样,我也希望至少有一些2级任务已经开始。他们从来没有这样做过
示例:
我创建了一个示例控制台应用程序,正好演示了这种行为:
按原样运行它,您将看到它首先安排所有任务,然后才开始从Level2任务中写入实际行
但是注释掉标记的代码块,取消对替换和所有工作的注释
你能告诉我为什么吗
public class Program
{
static void Main(string[] args)
{
for (var i = 0; i < 100; i++)
{
Task.Factory.StartNew(() => SomeMethod());
//Thread.Sleep(1000);
}
Console.ReadLine();
}
private static void SomeMethod()
{
var numbers = new List<int>();
for (var i = 0; i < 10; i++)
{
numbers.Add(i);
}
var tasks = new List<Task>();
foreach (var number in numbers)
{
Console.WriteLine("Before start task");
var numberSafe = number;
/* Code to be replaced START */
var nextTask = Task.Factory.StartNew(() =>
{
Console.WriteLine("Got number: {0}", numberSafe);
})
.ContinueWith(task =>
{
Console.WriteLine("Continuation {0}", task.Id);
});
tasks.Add(nextTask);
/* Code to be replaced END */
/* Replacement START */
//var originalTask = Task.Factory.StartNew(() =>
//{
// Console.WriteLine("Got number: {0}", numberSafe);
//});
//var contTask = originalTask
// .ContinueWith(task =>
// {
// Console.WriteLine("Continuation {0}", task.Id);
// });
//tasks.Add(originalTask);
//tasks.Add(contTask);
/* Replacement END */
}
Task.WaitAll(tasks.ToArray());
}
}
公共类程序
{
静态void Main(字符串[]参数)
{
对于(变量i=0;i<100;i++)
{
Task.Factory.StartNew(()=>SomeMethod());
//睡眠(1000);
}
Console.ReadLine();
}
私有静态方法()
{
变量编号=新列表();
对于(变量i=0;i<10;i++)
{
增加(i);
}
var tasks=新列表();
foreach(数值中的变量编号)
{
Console.WriteLine(“启动前任务”);
var numberSafe=数量;
/*要替换的代码开始*/
var nextTask=Task.Factory.StartNew(()=>
{
WriteLine(“获取编号:{0}”,numberSafe);
})
.ContinueWith(任务=>
{
WriteLine(“Continuation{0}”,task.Id);
});
任务。添加(nextTask);
/*待替换代码结束*/
/*更换起动*/
//var originalTask=Task.Factory.StartNew(()=>
//{
//WriteLine(“获取编号:{0}”,numberSafe);
//});
//var contTask=originalTask
//.ContinueWith(任务=>
// {
//WriteLine(“Continuation{0}”,task.Id);
// });
//任务。添加(originalTask);
//任务。添加(contTask);
/*替换端*/
}
Task.WaitAll(tasks.ToArray());
}
}
我必须说,这段代码确实不乐观,因为您创建了100个任务,但这并不意味着您将有100个线程,并且在每个任务中创建了两个新任务,您对调度程序的分配过多。如果这些任务与数据库读取相关,为什么不将它们标记为长处理并放弃内部任务?我认为您看到了任务内联行为。引述自:
在某些情况下,当任务处于等待状态时,它可能会在执行等待操作的线程上同步执行。这提高了性能,因为它通过利用现有线程(否则会被阻塞)来防止需要额外的线程。为了防止由于重新进入而导致错误,只有在相关线程的本地队列中找到等待目标时,才会发生任务内联
你不需要100个任务就能看到这一点。我已经修改了你的程序,使其具有4个1级任务(我有四核CPU)。每个级别1任务仅创建一个级别2任务
static void Main(string[] args)
{
for (var i = 0; i < 4; i++)
{
int j = i;
Task.Factory.StartNew(() => SomeMethod(j)); // j as level number
}
}
下面是示例输出-关于注释tasks.Add(originalTask)代码>-这是您的第一个块
Before start task: 0 - thread 4
Before start task: 2 - thread 3
Before start task: 3 - thread 6
Before start task: 1 - thread 5
Got number: 0 - thread 7
Continuation 0 - thread 7
Got number: 1 - thread 7
Continuation 1 - thread 7
Got number: 3 - thread 7
Continuation 3 - thread 7
Got number: 2 - thread 4
Continuation 2 - thread 4
和一些示例输出-关于保持任务.Add(originalTask)代码>哪一个是您的第二个块
Before start task: 0 - thread 4
Before start task: 1 - thread 6
Before start task: 2 - thread 5
Got number: 0 - thread 4
Before start task: 3 - thread 3
Got number: 3 - thread 3
Got number: 1 - thread 6
Got number: 2 - thread 5
Continuation 0 - thread 7
Continuation 1 - thread 7
Continuation 3 - thread 7
Continuation 2 - thread 4
正如您在第二种情况下看到的,当您在启动它的同一线程上等待originalTask
时,任务内联将使它在同一线程上运行-这就是为什么您在前面看到get Number..
消息的原因。如果我没记错的话,等待尚未计划的任务可能会同步执行。(请参阅)在另一种情况下,这种行为将应用于您的代码也就不足为奇了
请记住,线程行为是高度依赖于实现和机器的,这里发生的事情可能是这样的:
- 考虑到对线程池的调用和任务的实际执行之间的延迟,大多数所谓的“级别1”任务(如果不是全部的话)都是在第一个任务实际执行之前安排的
- 由于默认任务计划程序使用.NET线程池,因此此处计划的所有任务都可能在线程池线程上执行
- 一旦执行了“1级”任务,调度队列中就会充满“1级”任务
- 每次执行“1级”任务时,它都会根据需要安排尽可能多的“2级”任务,但这些任务都安排在“1级”任务之后
- 当“级别1”任务到达等待“级别2”任务的所有延续的点时,执行线程进入等待状态
- 有许多线程池
Before start task: 0 - thread 4
Before start task: 1 - thread 6
Before start task: 2 - thread 5
Got number: 0 - thread 4
Before start task: 3 - thread 3
Got number: 3 - thread 3
Got number: 1 - thread 6
Got number: 2 - thread 5
Continuation 0 - thread 7
Continuation 1 - thread 7
Continuation 3 - thread 7
Continuation 2 - thread 4
static void Main(string[] args)
{
for (var i = 0; i < 100; i++)
{
Task.Factory.StartNew(() => SomeMethod(),
TaskCreationOptions.LongRunning);
}
Console.ReadLine();
}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
public class Program
{
static void Main(string[] args)
{
var tasks = new List<Task>();
for (var i = 0; i < 100; i++)
{
tasks.Add(Task.Factory.StartNew(() => SomeMethod(i)).Unwrap());
}
// blocking at the topmost level
Task.WaitAll(tasks.ToArray());
Console.WriteLine("Enter to exit...");
Console.ReadLine();
}
private static Task<Task[]> SomeMethod(int n)
{
Console.WriteLine("SomeMethod " + n);
var numbers = new List<int>();
for (var i = 0; i < 10; i++)
{
numbers.Add(i);
}
var tasks = new List<Task>();
foreach (var number in numbers)
{
Console.WriteLine("Before start task " + number);
var numberSafe = number;
var nextTask = Task.Factory.StartNew(() =>
{
Console.WriteLine("Got number: {0}", numberSafe);
})
.ContinueWith(task =>
{
Console.WriteLine("Continuation {0}", task.Id);
}, TaskContinuationOptions.ExecuteSynchronously);
tasks.Add(nextTask);
}
return Task.Factory.ContinueWhenAll(tasks.ToArray(),
result => result, TaskContinuationOptions.ExecuteSynchronously);
}
}