Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
.net 使用TPL中预期的双线程对每个线程进行并行_.net_C# 4.0_Task Parallel Library_Threadpool_Parallel.foreach - Fatal编程技术网

.net 使用TPL中预期的双线程对每个线程进行并行

.net 使用TPL中预期的双线程对每个线程进行并行,.net,c#-4.0,task-parallel-library,threadpool,parallel.foreach,.net,C# 4.0,Task Parallel Library,Threadpool,Parallel.foreach,我将从一个基本的解释开始,解释我是如何理解一些需要解决的问题的,然后用tldr来总结这一切;如果人们只是想达到我这里的实际问题。如果我对这里的理解有误,请纠正我 TPL代表任务并行库,它是.NET4.0试图进一步简化线程以便于开发人员使用的答案。如果您不熟悉它,(在一个非常基本的级别上),您可以启动一个新的任务对象,并向它传递一个委托,该委托将在从线程池中获取的后台线程上运行(通过使用线程池而不是真正创建一个新线程,通过使用这些现有线程而不是创建和处理新线程来节省时间和资源) 据我所知,C#中的

我将从一个基本的解释开始,解释我是如何理解一些需要解决的问题的,然后用tldr来总结这一切;如果人们只是想达到我这里的实际问题。如果我对这里的理解有误,请纠正我

TPL代表任务并行库,它是.NET4.0试图进一步简化线程以便于开发人员使用的答案。如果您不熟悉它,(在一个非常基本的级别上),您可以启动一个新的任务对象,并向它传递一个委托,该委托将在从线程池中获取的后台线程上运行(通过使用线程池而不是真正创建一个新线程,通过使用这些现有线程而不是创建和处理新线程来节省时间和资源)

据我所知,C#中的Parallel.ForEach命令将为它应该执行的每个委托生成一个新线程(可能来自线程池),但可能的例外情况是,如果编译器决定它们将以足够快的速度执行,从而提高效率,则会自动内联一个或更多的迭代

与我的目标最相关的背景信息:

我正在尝试制作一个快速的程序,它启动一个任务并与程序的其余部分同时运行。在此任务中,Parallel.ForEach运行3次“迭代”。我们预计程序现在总共运行5个线程(最多):1个用于主线程,1个用于实际任务,最多3个用于Parallel.ForEach。每个线程都有自己的目标要完成(尽管Parallel.ForEach都有相同的目标,但计算的相关itemNumber的值不同。当主线程完成所有目标时,它使用Task.Wait()等待完成任务,该任务同时等待Parallel.ForEach完成。然后使用并验证这些值

tldr;实际问题:

当运行上述想法时,Parallel.ForEach初始化的SynchronizationContext(本质上是另一个线程的TPL对象)的数量似乎是我预期的两倍,并运行所有的SynchronizationContext,但是只等待预期数量的SynchronizationContext命令在预期的运行线程数量时结束,然后任务也会在认为一切都已完成时结束。然后主程序会检测到任务已完成,并验证当前没有更多的后台线程在运行,偶尔会检测到剩余的Parralel.ForEach()尚未完成,因此会抛出错误

在每次SynchronizationContext的post调用(异步方法kicker)时,通过打印到调试窗口,已验证线程的数量与我所述的相符。每个线程也由一个主线程对象引用,该主线程对象计划在完成任务时进行处置,但由于未完成的线程没有真正创建,因此仍然存在引用,因此处置无法正常进行

Thread testThread = Thread.CurrentThread;
Task backgroundTask = taskFactory.StartNew(() =>
{
    Thread rootTaskThread = Thread.CurrentThread;
    Assert.AreNotEqual(testThread, rootTaskThread, "First task should not inline");
    Thread.Sleep(TimeSpan.FromSeconds(2));

    Parallel.ForEach(new[] { 1, 2, 3, 4 },
       new ParallelOptions { TaskScheduler = taskFactory.Scheduler }, (int item) => {
        Thread.Sleep(TimeSpan.FromSeconds(1));
     });
});
在上面的示例中,主线程、backgroundTask和8个Parallel.ForEach线程最终存在,其中最后9个线程是在SynchronizationContext上创建的

在SynchronizationContext中为我的自定义重写的唯一方法是post,如下所示:

public override void Post(SendOrPostCallback d, object state){
    Request requestOrNull = Request.ExistsForCurrentThread() ? Request.GetForCurrentThread() as Request : null;
    Request.IAsyncContextData requestData = null;

    if (requestOrNull != null){
       requestData = requestOrNull.CaptureDataForNewThreadAndIncrementReferenceCount();
    }

    Debug.WriteLine("Task started - request data " + (requestData == null ? "DOES NOT EXIST" : "EXISTS"));

    base.Post((object internalState) => {
        // Capture the spawned thread state and restore the originating thread state
        try{
            if (requestData != null){
                Request.AttachToAsynchronousContext(requestData);
            }
            d(state);
        }
        finally{
            // Restore original spawned thread state
            if (requestData != null){
            // Disposes the request if this is the last reference to it
                Request.DetachFromAsynchronousContext(requestData);
            }
        Debug.WriteLine("Task completed - request data " + (requestData == null ? "DOES NOT EXIST" : "EXISTS"));
        }
    }, state);
 }
我认为TaskScheduler只做了基本的工作:

private readonly RequestSynchronizationContext context;
private readonly ConcurrentQueue<Task> tasks = new ConcurrentQueue<Task>();

public RequestTaskScheduler(RequestSynchronizationContext synchronizationContext)
{
    this.context = synchronizationContext;
}

protected override void QueueTask(Task task){
    this.tasks.Enqueue(task);
    this.context.Post((object state) => {
        Task nextTask;
        if (this.tasks.TryDequeue(out nextTask)) 
            this.TryExecuteTask(nextTask);
    }, null);
}

protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued){
    if (SynchronizationContext.Current == this.context)
        return this.TryExecuteTask(task);
    else
        return false;
}

protected override IEnumerable<Task> GetScheduledTasks(){
    return this.tasks.ToArray();
}

关于为什么会发生这种情况,您有什么想法吗?

任务本身不会创建线程。如果可以,任务调度器可以决定如何使操作异步。例如,某些操作使用异步IO,在这种情况下,硬件使其异步,而不是另一个工作线程

调用continuations的方式取决于同步上下文。该上下文不是另一个线程,它只是抽象了可以运行操作的条件。例如,在WPF、WinForms、Silverlight等中,有一个UI同步上下文,需要在特定线程上执行操作(UI线程或主线程,以避免异常)

ForEach将尝试创建线程(更具体地说,它将尝试询问同步上下文以启动多个异步操作)。调度程序定义了它的实际工作方式。如果您给它三个任务,它可能会创建三个线程,也可能不会。它决定三个并发线程是否是一件好事。例如,如果您只有两个内核,ForEach将不会创建两个以上的线程,因为这可能比使用单个线程创建多个线程更糟糕d由于上下文切换开销,按顺序运行代码

不清楚“初始化两倍于SynchronizationContext”是什么意思。这些不是线程。你只是说它创建的线程比你预期的多?还是说调用Post的次数比你预期的多?SynchronizationContext类基于什么?(即,它的基类是什么)。基本操作在很大程度上定义了如何进行后期调用。它可能觉得需要创建另一个异步操作来跟踪其他操作…您如何让调度程序使用此上下文

SynchronizationContext早在TPL(最早出现在.NET 2.0中)之前就存在了。它所做的一件事就是管理异步操作请求。从您的帖子中不清楚您是否理解这一点

更新: 对QueueTask的第一个调用间接来自StartNew。 对QueueTask的第二个调用间接来自对ForEach的调用 对QueueTask的第三个调用间接来自QueueTask中的TryExecuteTask 接下来的4个QueueTask调用是针对传递给ForEach的主体的

根据负载的不同,QueueTask最多可被调用3次。如果我在QueueTask上调试并中断,QueueTask只会被调用7次

在这一点上,既然你
public RequestTaskFactory(RequestTaskScheduler taskScheduler)
    : base(taskScheduler)
{ }