-2

我必须处理大约 170.000 个文件,并且想使用多个线程。文件的名称按照年份-编号格式顺序排列,并在文件夹中按年份排序。(但它们可以都在同一个文件夹中)。不同的年份有不同的文件数。文件很小,每个只有几个 (10<size<20) KB。

它们的处理顺序无关紧要,因为处理任务的输出将存储在 SQL 数据库中。最好的方法是什么?不打开同一个文件两次?

4

5 回答 5

1

一种可能的解决方案是使用生产者/消费者设计模式。

您的生产者将获得文件列表并提供一些ProducerConsumer队列。您的消费者将处理从队列中取出的文件(或文件路径)并处理它(插入到您的数据库中)。使用这种方法,每个文件只会被处理一次。

C# producer/consumer SO question中描述了ProducerConsumer队列问题。

编辑

但是,任务可能会变得复杂,例如

  • 如果现有文件之一发生更改会发生什么情况。您是否必须使用新文件内容更新数据库?如果是这样,您将必须有一种“标记”机制,表示文件已更改(文件上次更新日期在某些情况下可能有效)
  • 如果在此过程中添加新文件会发生什么情况。等等
于 2013-01-14T16:18:50.820 回答
0

我会说每年1个线程。每个“年份线程”读取以该年份编号开头的文件,并按顺序读取它们。至于去数据库,我建议你要么

  • 如果全部转到单个表,请删除索引以便不会发生索引锁定,然后创建索引
  • 如果您无法删除索引,至少使用行锁定,并在超时前等待事务(两个或多个线程可能同时插入)

另一种解决方案是让线程生成对文件的插入语句,然后执行该文件以进行插入,或者您可以使用批量插入工具。但这取决于表结构和您的 DBMS

于 2013-01-14T16:19:38.650 回答
0

这是一个小例子:

public static class FilesProcessor
{
    private static List<FileProcessor> m_FileProcessors;

    public static void Start()
    {
        m_FileProcessors = new List<FileProcessor>();

        for (Int32 year = 2005; year < DateTime.Now.Year; ++year)
            InstanciateFileProcessor(year);

        while (!FinishedLoading())
            Application.DoEvents();
    }

    public static void Stop()
    {
        foreach (FileProcessor processor in m_FileProcessors)
            processor.Stop()

        m_FileProcessors.Clear();
        m_FileProcessors = null;
    }

    private static Boolean FinishedLoading()
    {
        foreach (FileProcessor processor in m_FileProcessors)
        {
            if (processor.IsAlive() && !processor.FinishedLoading())
                return false;
        }

        return true;
    }

    private static void InstanciateFileProcessor(Int32 year)
    {
        FileProcessor processor = new FileProcessor(year);
        processor.Start();

        m_FileProcessors.Add(processor);
    }
}

然后是 FileProcessor 类:

public sealed class FileProcessor
{
    private Int32 m_Year;

    public Boolean IsAlive()
    {
        return ((m_Thread != null) && m_Thread.IsAlive);
    }

    public Boolean FinishedLoading()
    {
        return ((m_Thread == null) || m_Thread.Join(10));
    }

    public FileProcessor(Int32 year)
    {
        m_Year = year;

        m_Thread = new Thread(Load);
        m_Thread.Name = "Background File Processor";
    }

    public void Start()
    {
        if (m_Thread != null)
            m_Thread.Start();
    }

    public void Stop()
    {
        if ((m_Thread != null) && m_Thread.IsAlive)
            m_Thread.Abort();
    }

    private void Load()
    {
        // Browse the Year folder...
        // Get and read all fines one by one...
    }
}
于 2013-01-14T16:22:12.180 回答
0

我可以在这里看到两种可能的方法。

首先,将您的问题一分为二。1 - 确定要处理的内容,2 - 进行处理。第 1 部分可能必须自己运行,因此您最终会得到一个 100% 准确的需要处理内容的列表。然后,您可以在拆分列表和引入多个线程方面实现花哨(或不太花哨)的逻辑。

其次,做一些类似于@CarlosGrappa 建议的事情。所以基本上你用自己的“预编程”过滤器创建每个线程。正如卡洛斯建议的那样,这可能是一年。或者,您可以创建 24 个线程,每个文件时间戳一个小时。或 60 个线程,每个线程查看一个小时后的特定分钟。它基本上可以是任何可以为您提供明确标准的东西(a)尽可能均匀地分配负载,以及(b)保证数据文件只处理一次。

显然,这些方法中的第二种会运行得更快,但是您必须对如何拆分文件进行一些额外的思考。使用第一种方法,一旦你得到了完整的列表,你基本上可以一次在你的处理器上处理 100、1000 或 10000 个等文件,而不会过于聪明地知道你是如何做到的。

于 2013-01-14T16:36:00.157 回答
0

使用 .Net 的并行类有什么问题?

只需将一个集合传递给并行的 foreach 循环。.Net 为您完成所有分配。您还可以传入自定义分区器,以便使用块分区。块分区导致线程不断请求更多任务。如果你不使用块分区,所有的工作都将被预先分配,当一些任务比其他任务花费更长的时间时会导致一些性能下降(这可能导致一些线程空闲而一个线程仍有工作要做)。

http://msdn.microsoft.com/en-us/library/dd460720.aspx

于 2013-01-14T16:47:29.587 回答