背景:
我有一个控制台应用程序,该应用程序创建Tasks
来处理来自数据库的数据(我们称它们为Level1任务)。每个任务都再次创建自己的任务,以处理分配给它的数据的每个部分(Level2任务)。
每个Level2任务都有一个与其关联的继续任务,以及WaitAll
在继续执行之前用于继续任务的代码。
我在.NET 4.0
(否async
/ await
)
问题:
但是,这产生了一个问题-事实证明,如果以此方式完成,则在计划所有可用的Level1任务之前,不会启动任何Level2任务。这绝不是最佳选择。
题:
更改代码以等待原始的Level2任务及其继续任务似乎已解决了该问题。但是,我不确定为什么会这样。
你有什么想法?
我唯一能想到的是-由于继续任务尚未开始,因此没有必要等待它完成。但是即使是这种情况,我也希望至少已经有一些Level2任务开始了。他们从来没有做过。
例:
我创建了一个示例控制台应用程序,演示了该行为:
按原样运行它,您会看到它首先调度了所有任务,然后才开始从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());
}
}
我认为您正在看到这种Task Inlining
行为。从MSDN引用:
在某些情况下,在等待任务时,可以在执行等待操作的线程上同步执行该任务。这样可以提高性能,因为它可以通过利用现有的可能会阻塞的线程来避免对其他线程的需求。为了防止由于重新进入而导致错误,仅当在相关线程的本地队列中找到等待目标时才发生任务内联。
您不需要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
}
}
在您的原始程序中,这nextTask
是继续任务-所以我只是简化了方法。
private static void SomeMethod(int num)
{
var numbers = new List<int>();
// create only one level 2 task for representation purpose
for (var i = 0; i < 1; i++)
{
numbers.Add(i);
}
var tasks = new List<Task>();
foreach (var number in numbers)
{
Console.WriteLine("Before start task: {0} - thread {1}", num,
Thread.CurrentThread.ManagedThreadId);
var numberSafe = number;
var originalTask = Task.Factory.StartNew(() =>
{
Console.WriteLine("Got number: {0} - thread {1}", num,
Thread.CurrentThread.ManagedThreadId);
});
var contTask = originalTask
.ContinueWith(task =>
{
Console.WriteLine("Continuation {0} - thread {1}", num,
Thread.CurrentThread.ManagedThreadId);
});
tasks.Add(originalTask); // comment and un-comment this line to see change in behavior
tasks.Add(contTask); // same as adding nextTask in your original prog.
}
Task.WaitAll(tasks.ToArray());
}
这是示例输出-注释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
还有一些示例输出-保持tasks.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
在启动它的同一线程上等待时,task inlining
它将使它在同一线程上运行-这就是为什么您Got Number..
较早看到消息的原因。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句