C# 通过并行类API中的ParallelOptions参数提供TaskScheduler选项的真正目的
我有一个windows C#WinForms应用程序。对于任务并行库中并行类的默认任务调度器的行为方式,我感到困惑。当我调用C# 通过并行类API中的ParallelOptions参数提供TaskScheduler选项的真正目的,c#,.net,c#-4.0,task-parallel-library,task,C#,.net,C# 4.0,Task Parallel Library,Task,我有一个windows C#WinForms应用程序。对于任务并行库中并行类的默认任务调度器的行为方式,我感到困惑。当我调用Parallel.ForinsideParallelForEach方法时,我希望通过控制台在输出窗口中以混乱的方式打印数字。WriteLine语句位于“Method1”方法中 因为我没有传递任何ParallelOptions来配置并行类的调度程序,所以它应该使用默认的任务调度程序,但是数字是以串行方式打印的,比如0,1,2,3,…49。这给我的印象是,正在执行的所有任务都是
Parallel.For
insideParallelForEach
方法时,我希望通过控制台在输出窗口中以混乱的方式打印数字。WriteLine
语句位于“Method1”方法中
因为我没有传递任何ParallelOptions
来配置并行类的调度程序,所以它应该使用默认的任务调度程序,但是数字是以串行方式打印的,比如0,1,2,3,…49。这给我的印象是,正在执行的所有任务都是以串行方式在UI同步上下文上逐个运行的。我还通过用I值更新UI文本框的text属性来验证这一点,它没有向我抛出invalidoOperationException
,这在线程池线程的情况下会发生
但是如果我取消注释Method1
函数中的第一条语句以模拟长时间运行的任务,那么输出就会变得混乱。为什么CLR决定使用UI线程来运行所有这50个任务?为什么CLR代表我做出这个决定,而我并没有要求它这么做,只是因为我的任务最初是小型计算任务?CLR如何真正评估特定方法执行时间长或执行时间短这一事实
硬件信息:我的电脑是一个Windows Server 2008机箱,有四个内核
namespace WindowsFormsApplication1
{
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Schedulers;
using System.Windows.Forms;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
ParallelForEach();
}
private void ParallelForEach()
{
Console.WriteLine("With default task scheduler");
Parallel.For(0, 50, i => Method1(i));
}
private void Method1(int i)
{
//System.Threading.Thread.Sleep(2000);
//textBox1.Text = i.ToString();
Console.WriteLine(i);
//do some work
}
}
}
默认情况下,
Parallel.For()
使用默认的TaskScheduler
(它使用ThreadPool
)和当前线程(它必须被阻止,直到所有迭代完成,所以它可能需要做一些有用的工作,而不是等待)。因此,如果有少量迭代快速完成,则整个循环可能会在当前循环上执行,甚至在任务调度器
调度的所有任务
启动之前。这解释了你在这两种情况下看到的行为
请注意,您不应该长时间阻塞UI线程,这样做会冻结应用程序的UI。这意味着您不应该在UI线程上使用
Parallel.For()
。相关-。您可能会尝试深入研究,但我实际上怀疑是否存在一些超级聪明的算法(一些启发式算法依赖于内部和外部因素,但仅此而已),尽管有可能部分算法隐藏在外部实现方法中,在这种情况下,.NET内核可能更有用-,我怀疑SynchronizationContext是否与您遇到的行为有关。最可能的情况是,Paraller.For尝试在原始线程上执行部分任务,并且仅在需要超过特定时间的情况下才开始使用其他线程。您的意思是-线程完成迭代的速度太快,它会很快拿起下一个任务?在另一个例子中,另一个线程拾取下一个任务,而第一个线程被阻塞在Sleep()
?@shay_uuuuu某种程度上,除了并行之外。对于(0,50,…)
,并不意味着有50个任务。因此,在任务
被分配线程之前,主线程完成了所有迭代。@svick我也有类似的印象,但我只是在读到ParallelOptions类时感到困惑,该类可以作为参数传递给Parallel.For方法。ParallelOptions类有一个属性“TaskScheduler”,用于选择所选的指定TaskScheduler。其默认值为“TaskScheduler.default”。现在我明白了你的观点,主线程的这种使用哲学适用于并行类的所有方法,而不管我们如何配置与之相关的调度器。我又做了一件事,将循环计数器增加到500,然后输出变得混乱:)@shay_uuu只是为了添加svick提到的内容。作为默认行为,任务调度器通常调度与计算机上CPU核数量相同的任务(在我的例子中是4个)。这可以通过ParallelOptions类进行配置,ParallelOptions类可以作为参数传递给Parallel.For method。实际上,这是一个很好的例子,说明基于任务的并行不仅比基于线程的并行容易,而且效率更高!(第一个示例,没有Sleep()
)