在c# .Net 3.5中,我如何控制多个WaitHandles?

3
我有以下场景:

我的应用程序必须从另一个应用程序中导入多个数据集,时间非常关键。
因此,它为每次导入生成一个线程。
因此,假设我有1到10次导入,其中4和5只能在导入1之后运行,6和7在导入2之后运行,8、9和10在导入3之后运行。
  • 导入1
    • 导入4
    • 导入5
  • 导入2
    • 导入6
    • 导入7
  • 导入3
    • 导入8
    • 导入9
    • 导入10
在stackoverflow上搜索了一些waithandles的答案,但我不确定如何控制多个waithandles。
我考虑创建一个导入/处理列表,并在循环中检查它们,但我不知道如何从那里触发.set()。
或者为每个父级导入创建一个线程并在其中实例化处理程序,该父线程为每个子级导入触发一个线程,但是处理线程的线程是否正确是我不确定的。

你有什么解决方法吗?

更新:
在Brian Gideon的回答之后,我想到了以下解决方法:
dateSince = Convert.ToDateTime(txtBoxDateSince.Text); dateTo = Convert.ToDateTime(txtBoxDateTo.Text);

//循环时间间隔内的所有日期 while (DateTime.Compare(dateSince, dateTo) <= 0) {

foreach (ListItem father in checkBoxListFather.Items)
{
    if (father.Selected == true)
    {
        processClass process = new processClass();

        // This WaitHandle will be used to get the child tasks going.
        var wh = new ManualResetEvent(false);

        //Method to Import, wraped in a delegate
        WaitCallback fatherMethod = new WaitCallback(process.importProcess);
        //and its parameters
        processClass.importParameters param = new processClass.importParameters(wh, father.Value, null, dateSince);

        // Queue the parent task.
        ThreadPool.QueueUserWorkItem(fundMethod, param);

        // Register the child tasks.
        foreach (ListItem child in checkBoxListChild.Items)
        {
            if (child.Selected == true)
            {
                processClass.importParameters parameters = new processClass.importParameters(null, child.Value, null, dateSince);

                // Registers a callback for the child task that will execute once the
                // parent task is complete.
                WaitOrTimerCallback childMethod = new WaitOrTimerCallback(process.anotherImportProcess);
                RegisteredWaitHandle rwh = ThreadPool.RegisterWaitForSingleObject(wh, childMethod, parameters, Timeout.Infinite, true);

            }//End if (child.Selected == true)

        }//End foreach (ListItem fund in checkBoxListChild.Items)

    }//End if (father.Selected == true)

}//End foreach (ListItem fundProcess in checkBoxListFather.Items)

dateSince = dtSince.AddDays(1);

}//在(DateTime.Compare(since,to) < 0)结束while循环

基本上是相同的答案,只是使用了没有lambda表达式的方法,并在它们上使用了参数。
我仍然没有进行压力测试,但它的工作非常好。

谢谢Brian。

2个回答

2

嗯,只有一些是线性的,而且我需要排队以实现可扩展性。我检查了两种方法,但问题是要检查某些线程,以启动另一组线程,而不是全部线程。 - Huggy

1

如果您真的想为每个任务分配一个线程,那么根本不需要使用WaitHandle实例。1 您可以将执行父任务的Thread实例传递给每个子任务,并调用Join以确保父任务已完成。

void TaskEntryPoint(Thread parent, ImportTask task)
{
  if (parent != null)
  {
    parent.Join(); // Wait for the parent task to complete.
  }
  task.Execute(); // Execute the child task.
}

现在你需要做的就是让父任务在它们自己的线程上运行,然后让子任务在它们自己的线程上运行。当调用 TaskEntryPoint 时,如果是父任务,则传递 null,如果是每个子任务,则传递相应的 Thread 实例。 更新: 根据您的评论,以下是我使用 ThreadPool 解决问题的示例。这是一种相当高级的模式,使用 ThreadPool.RegisterWaitForSingleObject 方法。它也是一种极其可扩展的解决方案,因为它使用绝对最少的资源来等待 WaitHandle 被触发。
foreach (ImportTask parent in parentTasks)
{
    // This WaitHandle will be used to get the child tasks going.
    var wh = new ManualResetEvent(false);

    // Needed to capture the loop variable correctly.
    var p = parent; 

    // Queue the parent task.
    ThreadPool.QueueUserWorkItem(
        (state) =>
        {
            try
            {
                // Execute the parent task.
                p.Execute();
            }
            finally
            {
                // Signal the event so that the child tasks can begin executing.
                wh.Set();
            }
        }, null);

    // Register the child tasks.
    foreach (ImportTask child in parent.ChildTasks)
    {
        // Needed to capture the loop variable correctly.
        var c = child;

        // Registers a callback for the child task that will execute once the
        // parent task is complete.
        RegisteredWaitHandle rwh = ThreadPool.RegisterWaitForSingleObject(wh,
            (state, to) =>
            {
                // Execute the child task.
                c.Execute();
            }, 
            null, Timeout.Infinite, true);
    }
}

这里的魔法在于RegisterWaitForSingleObject等待事件的方式。它将注册一个回调函数,一旦事件被触发就会自动执行。但是,它以这样的方式执行,使得池中没有任何线程浪费在等待该事件上。再次强调,这是一个相当复杂的模式,需要您认真思考,但它将非常可扩展。


1为每个任务启动一个新线程可能不是最佳策略。您是否考虑过使用ThreadPoolTask类?


对不起,Brian。我没有明确说明,但我正在使用ThreadPool来管理线程,因为在一年左右的时间里,我可能会为此进程创建200多个线程。我仍然可以使用您的逻辑与ThreadPool吗? - Huggy
非常感谢您的回答。我会尝试实现它并让您知道结果。只有一个问题。我对lambda表达式不是很熟悉,那么在(state,to)=>中的“to”是什么意思? - Huggy
@Huggy:它是WaitOrTimerCallback所需的参数之一。在我的示例中未使用它。当然,您不必使用lambda表达式。如果您喜欢,可以单独定义方法。 - Brian Gideon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接