VB.NET,排序线程池并发结果的最佳实践?

时间:2013-09-11 21:34:23

标签: vb.net multithreading performance concurrency

简而言之,我正在尝试“排序”使用线程池的传入结果。我有一个功能性解决方案,但世界上没有办法,这是最好的方法(它容易出现大停顿)。所以我来了!我将尝试找出正在发生的事情/需要发生什么的要点然后发布我当前的解决方案。

  • 代码的目的是获取有关目录中文件的信息,然后将其写入文本文件。

  • 我有一个列表(Counter.ListOfFiles),它是以特定方式排序的文件路径列表。这是指示我需要写入文本文件的顺序的指南。

  • 我正在使用线程池来收集有关每个文件的信息,创建一个stringbuilder,其中包含准备写入文本文件的所有文本。然后我调用一个过程(SyncUpdate,如下所示),从该线程发送stringbuilder(strBld)以及特定线程刚写入的文件路径的名称(Xref)。

  • 该过程包括一个synclock,用于保存所有其他线程,直到找到传递正确信息的线程。当线程传递的外部参照与列表中的第一个项(FirstListItem)匹配时,“正确”信息。当发生这种情况时,我写入文本文件,删除列表中的第一项,并使用下一个线程再次执行。

我使用显示器的方式可能不是很好,事实上我毫不怀疑我是以进攻性的方式使用它。基本上,当外部参照(来自线程)<>我列表中的第一项,我正在为显示器做一个脉冲。我最初使用的是monitor.wait,但它最终会放弃尝试对列表进行排序,即使在别处使用脉冲时也是如此。我可能刚刚编写了一些笨拙的东西。无论哪种方式,我认为它不会改变任何东西。

基本上问题归结为这样一个事实,即监视器会脉冲通过队列中的所有项目,而我正在寻找的项目很可能会在队列中的某个地方或其他任何地方传递给它。它现在将重新排序所有项目,然后循环回来找到匹配的条件。结果是我的代码将触及其中一个并花费大量时间来完成。

我愿意相信我只是在使用错误的工具,或者只是没有使用我正确使用的工具。我非常喜欢某种线程解决方案(不出所料,它更快!)。我今天一直在使用Parallel Task功能搞乱一些,而且很多东西看起来很有希望,但我对线程池的经验甚至更少,你可以看到我是如何滥用它的!也许有队列的东西?你明白了。我无方向。任何人都可以建议的东西将非常感激。谢谢!如果您需要任何其他信息,请与我们联系。

  Private Sub SyncUpdateResource(strBld As Object, Xref As String)
    SyncLock (CType(strBld, StringBuilder))
        Dim FirstListitem As String = counter.ListOfFiles.First
        Do While Xref <> FirstListitem
            FirstListitem = Counter.ListOfFiles.First
    'This makes the code much faster for reasons I can only guess at.
            Thread.Sleep(5)
            Monitor.PulseAll(CType(strBld, StringBuilder))
        Loop
        Dim strVol As String = Form1.Volname
        Dim strLFPPath As String = Form1.txtPathDir
        My.Computer.FileSystem.WriteAllText(strLFPPath & "\" & strVol & ".txt", strBld.ToString, True)
        Counter.ListOfFiles.Remove(Xref)
    End SyncLock
End Sub

1 个答案:

答案 0 :(得分:3)

这是一个非常典型的多生产者,单一消费者应用程序。唯一的缺点是你必须在将结果写入输出之前对结果进行排序。这不难做到。所以让我们暂时放下这个要求。

.NET中实现生产者/消费者关系的最简单方法是BlockingCollection,这是一个线程安全的FIFO队列。基本上,你这样做:

在您的情况下,生产者线程获取项目,执行他们需要的任何处理,然后将项目放入队列。不需要任何显式同步 - BlockingCollection类实现为您做到了。

您的消费者从队列中提取物品并将其输出。您可以在我的文章Simple Multithreading, Part 2中看到一个非常简单的示例。 (如果您只对代码感兴趣,请向下滚动到第三个示例。)该示例仅使用一个生产者和一个消费者,但如果需要,您可以拥有N个生产者。

您的要求有点皱纹,因为消费者不能只在文件中写入项目。它必须确保它以正确的顺序编写它们。正如我所说,这并不难做到。

你想要的是某种优先级队列,如果它出现故障,你可以放置一个项目。如果您希望失序的项目数量不是很大,那么您的优先级队列可以是排序列表,甚至只是一个顺序列表。也就是说,如果您一次只有六个项目可能出现故障,那么顺序列表可以正常工作。

我使用堆,因为它表现良好。 .NET Framework不提供堆,但我有一个简单的适用于这样的工作。请参阅A Generic BinaryHeap Class

所以这就是我如何编写消费者(代码是伪C#,但你可以很容易地转换它)。

这里的假设是您有一个名为BlockingCollection的{​​{1}}包含项目。生产者将物品放在该队列上。消费者这样做:

sharedQueue

编写此方法的一个明显问题是,如果您的某个消费者崩溃或进入无限循环,堆可能无限制地增长。但是,你的另一种方法可能也会受到影响。如果您认为这是一个问题,您将不得不添加一些方法来跳过您认为永远不会出现的项目。或者杀死程序。或者其他什么。

如果您没有二进制堆或不想使用二进制堆,则可以使用var heap = new BinaryHeap<ItemType>(); foreach (var item in sharedQueue.GetConsumingEnumerable()) { if (item.SequenceKey == expectedSequenceKey) { // output this item // then check the heap to see if other items need to be output expectedSequenceKey = expectedSequenceKey + 1; while (heap.Count > 0 && heap.Peek().SequenceKey == expectedSequenceKey) { var heapItem = heap.RemoveRoot(); // output heapItem expectedSequenceKey = expectedSequenceKey + 1; } } else { // item is out of order // put it on the heap heap.Insert(item); } } // if the heap contains items after everything is processed, // then some error occurred. 执行相同的操作。如果列表中的项目数量甚至相当大(几十个),SortedList<ItemType>将比SortedList快,但会慢于List。少于此,它可能是洗涤。

我知道这是很多信息。我很乐意回答您的任何问题。