C# 使用生产者/消费者模式和SQLServerDB中的SqlBulkCopy,使用多个线程处理分块的平面文件

C# 使用生产者/消费者模式和SQLServerDB中的SqlBulkCopy,使用多个线程处理分块的平面文件,c#,sql-server,multithreading,flat-file,sqlbulkcopy,C#,Sql Server,Multithreading,Flat File,Sqlbulkcopy,我希望你能容忍我。我想提供尽可能多的信息。 主要问题是如何创建一个由多个线程使用的结构(如堆栈),这些线程将弹出一个值并使用它来处理一个大的平面文件,并且可能一次又一次地循环,直到处理完整个文件。 如果一个文件有100.000条记录,可由5个线程使用2.000行块处理 然后每个线程将得到10个块来处理 我的目标是在平面文件中移动数据(标题…子标题…详细信息,详细信息,详细信息,…详细信息,子标题,子标题…详细信息,详细信息,详细信息,…详细信息,子标题, Subheader…Detail,Det

我希望你能容忍我。我想提供尽可能多的信息。 主要问题是如何创建一个由多个线程使用的结构(如堆栈),这些线程将弹出一个值并使用它来处理一个大的平面文件,并且可能一次又一次地循环,直到处理完整个文件。 如果一个文件有100.000条记录,可由5个线程使用2.000行块处理 然后每个线程将得到10个块来处理

我的目标是在平面文件中移动数据(标题…子标题…详细信息,详细信息,详细信息,…详细信息,子标题,子标题…详细信息,详细信息,详细信息,…详细信息,子标题, Subheader…Detail,Detail,Detail,…Detail,suboter,Footer structure)转换为OLTP DB,恢复模式为Simple(可能是Full),分为3个表:第一个表代表Subheader行中存在的Subheader的唯一键,第二个表是中间表SubheaderGroup,代表2000条记录块中细节行的分组(需要将子标题的标识PK作为其FK,第3行表示FK指向子标题PK的明细行

我正在进行手动事务管理,因为我可以有成千上万的详细信息行 在加载过程中,我在目标表中使用一个特殊字段,该字段设置为0,然后在文件处理结束时,我正在执行一个事务更新,将该值更改为1,这可以向其他应用程序发出加载已完成的信号

我想将这个平面文件切分为多个相等的部分(相同数量的行),这些部分可以用多个线程处理,并使用从目标表元数据创建的IDataReader使用SqlBulkCopy导入

我想使用生产者/消费者模式(如下面链接-pdf分析和代码示例中所述)将SqlBulkCopy与SqlBulkCopyOptions.TableLock选项一起使用。 此模式允许创建多个生产者,并且需要订阅生产者才能使用该行的同等数量的消费者

在TestSqlBulkCopy项目的DataProducer.cs文件中,有一种方法可以模拟数千条记录的生成

public void Produce (DataConsumer consumer, int numberOfRows) {
    int bufferSize = 100000;
    int numberOfBuffers = numberOfRows / bufferSize;

    for (int bufferNumber = 0; bufferNumber < numberOfBuffers; bufferNumber++) {
        DataTable buffer = consumer.GetBufferDataTable ();

        for (int rowNumber = 0; rowNumber < bufferSize; rowNumber++) {
            object[] values = GetRandomRow (consumer);
            buffer.Rows.Add (values);
        }
        consumer.AddBufferDataTable (buffer);
    }
}
public void product(数据消费者,int numberOfRows){
int bufferSize=100000;
int numberOfBuffers=numberOfRows/bufferSize;
对于(int bufferNumber=0;bufferNumber
此方法将在新线程的上下文中执行。我希望此新线程仅读取原始平面文件的唯一区块,另一个线程将开始处理下一个区块。然后,使用者将使用SqlBulkCopy ADO.NET类将数据(泵送到他们的数据)移动到SQL Server DB

所以这里的问题是关于主程序,它规定了每个线程应该处理从lineFrom到lineTo的内容,我认为这应该在线程创建期间发生。 第二种解决方案可能是让线程共享某些结构,并使用它们独有的东西(如线程编号或序列号)查找共享结构(可能是堆栈并弹出值(执行此操作时锁定堆栈)然后下一个线程将拾取下一个值。主程序将拾取平面文件并确定块的大小并创建堆栈

那么,有没有人能提供一些代码片段,比如多个线程如何处理一个文件而只获取该文件的唯一部分

谢谢,
Rad

对我来说,最有效的方法是使用队列保存未处理的工作,使用字典跟踪正在进行的工作:

  • 创建一个接受 文件名、起始行和行数 并且有一个更新方法 数据库是否插入。传递 工作人员在完成时使用信号
  • 加载包含工作进程实例的队列 类,每个块一个
  • 生成一个调度程序线程,使 worker实例,启动其更新 方法,并将工作者实例添加到字典中,由其线程的ManagedThreadId设置关键字 直到你的最大允许线程 已达到计数,如 Dictionary.Count.调度员 等待线程完成 然后再启动另一个。有几种方法可以让它等待
  • 当每个线程完成时,它的回调 从中删除其ManagedThreadId 字典。如果线程退出 由于错误(例如 连接超时)然后 回调可以重新插入工作进程 排队。这是个好地方 更新你的用户界面
  • 您的UI可以显示活动线程、总进度和每个区块的时间。它可以让用户调整活动线程的数量、暂停处理、显示错误或提前停止
  • 当队列和字典为空时,就完成了
  • 作为控制台应用程序的演示代码:

    using System;
    using System.Collections.Generic;
    using System.Threading;
    
    namespace threadtest
    {
        public delegate void DoneCallbackDelegate(int idArg, bool successArg, string messageArg);
    
        class Program
        {
            static void Main(string[] args)
            {
                Supervisor supv = new Supervisor();
                supv.LoadQueue();
                supv.Dispatch();
            }
        }
    
        public class Supervisor
        {
            public Queue<Worker> pendingWork = new Queue<Worker>();
            public Dictionary<int, Worker> activeWork = new Dictionary<int, Worker>();
    
            private object pendingLock = new object();
            private object activeLock = new object();
    
            private int maxThreads = 200;
    
            public void LoadQueue()
            {
                for (int i = 0; i < 1000; i++)
                {
                    Worker worker = new Worker();
                    worker.Callback = new DoneCallbackDelegate(WorkerFinished);
                    lock (pendingLock)
                    {
                        pendingWork.Enqueue(worker);
                    }
                }
            }
    
            public void Dispatch()
            {
                int activeThreadCount;
    
                while (true)
                {
                    lock (activeLock) { activeThreadCount = activeWork.Count; }
                    while (true)
                    {
                        lock (activeLock)
                        {
                            if (activeWork.Count == maxThreads) break;
                        }
                        lock (pendingWork)
                        {
                            if (pendingWork.Count > 0)
                            {
                                Worker worker = pendingWork.Dequeue();
                                Thread thread = new Thread(new ThreadStart(worker.DoWork));
                                thread.IsBackground = true;
                                worker.ThreadId = thread.ManagedThreadId;
                                lock (activeLock) { activeWork.Add(worker.ThreadId, worker); }
                                thread.Start();
                            }
                            else
                            {
                                break;
                            }
                        }
                    }
                    Thread.Sleep(200); // wait to see if any workers are done (many ways to do this)
    
                    lock (pendingLock)
                        lock (activeLock)
                        {
                            if ((pendingWork.Count == 0) && (activeWork.Count == 0)) break;
                        }
                }
            }
    
            // remove finished threads from activeWork, resubmit if necessary, and update UI
            public void WorkerFinished(int idArg, bool successArg, string messageArg)
            {
                lock (pendingLock)
                    lock (activeLock)
                    {
                        Worker worker = activeWork[idArg];
                        activeWork.Remove(idArg);
                        if (!successArg)
                        {
                            // check the message or something to see if you should resubmit thread
                            pendingWork.Enqueue(worker);
                        }
                        // update UI
                        int left = Console.CursorLeft;
                        int top = Console.CursorTop;
                        Console.WriteLine(string.Format("pending:{0} active:{1}        ", pendingWork.Count, activeWork.Count));
                        Console.SetCursorPosition(left, top);
                    }
            }
        }
    
        public class Worker
        {
            // this is where you put in your problem-unique stuff
            public int ThreadId { get; set; }
    
            DoneCallbackDelegate callback;
            public DoneCallbackDelegate Callback { set { callback = value; } }
    
            public void DoWork()
            {
                try
                {
                    Thread.Sleep(new Random().Next(500, 5000)); // simulate some effort
                    callback(ThreadId, true, null);
                }
                catch (Exception ex)
                {
                    callback(ThreadId, false, ex.ToString());
                }
            }
        }
    }
    
    使用系统;
    使用System.Collections.Generic;
    使用系统线程;
    命名空间线程测试
    {
    公共委托void DoneCallbackDelegate(int-idArg、bool-successArg、string-messageArg);
    班级计划
    {
    静态void Main(字符串[]参数)
    {
    主管supv=新主管();
    supv.LoadQueue();
    supv.Dispatch();
    }
    }
    公营班主任
    {
    public Queue pendingWork=新队列();
    公共字典activeWork=新字典();
    私有对象pendingLock=新对象();
    私有对象activeLock=新对象();
    私有int maxThreads=200;
    公共void加载队列()
    {
    对于(int i=0;i<1000;i++)
    {
    工人=新工人();
    worker.Callback=new DoneCallbackDelegate