【问题标题】:Executing multiple threads执行多个线程
【发布时间】:2016-07-21 12:27:16
【问题描述】:

我正在开发 Windows Form C# 程序,它每 20 分钟从共享驱动器中读取 Excel 数据(我使用的是“计时器”)- 功能“插入”。由于性能,我想一次读取多个 Excel 文件。出于这个原因,我正在使用线程。

每个线程都调用一个函数 (LoadExcelData),该函数将数据从 Excel 读取到 ArrayList。我想知道所有线程何时完成(当所有 excel 文件都加载到 ArrayList 时),以便将此 ArrayList 插入内部数据库。

我尝试使用 thread[i].Join() 但这会冻结 GUI。我也不知道如果我有 100 多个文件并且因此有 100 多个线程会发生什么。这会导致内存异常或其他异常吗?

        //Execute every 20 minutes  (Timer). Do not Execute in case previouse run is not finished
        void inserting(List<String> excels){

        int numOfThreads=excels.length;
        Thread[] threads = new Thread[numOfThreads];
        for (int index = 0; index < numOfThreads; index++)
        {
            int i = index;
            threads[index] = new Thread(() =>
                {
                    LoadExcelData(excels[i].File_name); //function loads excel data to global array "Weather" which is used later on
                });
        }

       for (int i = 0; i < threads.Length; i++)
        {
            threads[i].Start(); //start thread
        }

        for (int i = 0; i < threads.Length; i++)
        {
            //   threads[i].Join(); //this freezes GUI!
        }

       InsertToDB(object of ArrayList<ClassName>); //insert data which was read from Excels

       isRunning=false;//Data was successefully inserted to DB 
     }

我想每 20 分钟运行一次。我正在使用计时器:

    timer = new System.Windows.Forms.Timer();
    timer.Tick += new EventHandler(timerEventHanlder);
    timer.Interval = 20 * 60000; // in miliseconds
    timer.Start(); 

private void timerEventHanlder(object sender, EventArgs e)
{
   List<String> excels = getExcels();
    if (!isRunning){ //in case previous timer even is not finished wait another 20 minutes...
        isRunning=true; //flag to true
        inserting(excels);
       }
}

有没有更好的等待来解决上述问题?

【问题讨论】:

  • 就我个人而言,出于性能考虑,我几乎从不使用线程。我几乎总是使用它们来保持 UI 响应。实际上,我确实认为线程会降低性能,因为跨线程通信过度和锁定/同步。
  • 从一开始我就为同样的事情做这件事 - GUI 冻结了。但是后来我需要知道由于计时器的原因所有线程何时完成,并且我使用了导致 GUI 再次冻结的 thread.Join() 事件。在我的情况下,性能得到了提高,因为两个(或更多)Excel 文件的处理速度更快。

标签: c# multithreading winforms timer threadpool


【解决方案1】:

UI 线程正在冻结,因为您正在使用System.Windows.Forms.Timer,它会在 UI 线程上触发计时器计时事件;这很有用,因为您不必在滴答事件上Invoke 任何东西。调用 Join 会阻塞调用线程,在您的情况下这是 UI 线程。

为避免这种情况(并且由于您不需要 Invoke 任何 UI 元素),您可以将 System.Windows.Forms.Timer 更改为 System.Timers.Timer,它在与 UI 线程分开的线程中运行。如果您切换到 System.Timers.Timer,则需要更改代码中的一些语法(例如,Tick 事件改为 Elapsed 事件等)。

还有System.Thread.TimerSystem.Web.UI.Timer,此外,您还可以从计时器滴答事件中生成第二个线程,以避免它在 UI 线程中等待线程,例如:

private void timerEventHanlder(object sender, EventArgs e)
{
    (new System.Threading.Thread(() => {
        List<String> excels = getExcels();
        if (!isRunning){ //in case previous timer even is not finished wait another 20 minutes...
            isRunning=true; //flag to true
            inserting(excels);
        }
    })).Start();
}

启动新线程可避免更改任何当前代码,并允许您在需要调用 UI 中的任何内容时将其更改回来。

虽然回答你是另一个问题:

我也不知道如果我有 100 多个文件并且因此有 100 多个线程会发生什么。这会导致内存异常或其他异常吗?

生成 100 多个线程不会导致任何异常,除非您的代码有特定异常(例如作为 ThreadStart 传递的空委托),或者如果操作系统无法创建线程(如果操作系统可以) t创建一个线程你有更大的问题。由于Thread 是托管对象并因此占用内存(以及ArrayList,但100+ 线程(甚至1000+)的内存量在任何系统上都可以忽略不计,因此可能会发生内存耗尽。能够运行 .NET 框架(即使在大多数嵌入式系统上),因此线程数不一定是问题。

查看您的代码,您可能需要考虑使用System.Threading.ThreadPoolSystem.Threading.CountDownEvent,而不是生成100 多个线程,例如:

CountdownEvent Countdown;

void LoadExcelData(object data)
{
    // loads excel data to global array "Weather" which is used later on
    Countdown.Signal();
}

//Execute every 20 minutes  (Timer). Do not Execute in case previouse run is not finished
void inserting(List<object> excels)
{
    Countdown = new CountdownEvent(excels.Count); 
    int i = 0;
    while (i < excels.Count) {
        ThreadPool.QueueUserWorkItem(LoadExcelData, excels[i++].File_name);
    }
    Countdown.Wait();

    InsertToDB(WeatherList); //insert data which was read from Excels
    isRunning = false; //Data was successefully inserted to DB 
}

这将利用系统线程池来执行您的功能,并允许 .NET 处理线程的调度,以避免在线程数量很多时出现大量资源争用。您可以使用其他方法进行阻塞,例如 MutexSemaphore,但 CountDownEvent 几乎封装了您需要对其他等待对象执行的操作并加入线程池中的线程。

但老实说,由于您在多个线程中从 Excel 文件中读取数据,除非每个线程将文件的全部内容读取到 RAM 中然后以这种方式执行操作,否则您可能看不到性能的巨大提升。具有大量 I/O 的多线程应用程序通常不会看到巨大的性能提升,除非所述 I/O 是在注重性能的设备上,或者整个文件的初始输入被读入 RAM。只是一个旁注,因为您正在对文件进行多线程处理。

还应该注意的是,使用System.Threading.ThreadPool 非常适合您希望只运行几秒钟左右的线程;如果您预计线程可能需要更长的时间,您应该像现在一样坚持生成线程。您仍然可以使用CountDownEvent,并且不需要像您拥有的线程数组(您可以只使用(new Thread(function)).Start() 语法)。

希望能帮到你

【讨论】:

  • 谢谢你的解释,真的很有帮助。我不知道 System.Timers.Timer 和 System.Windows.Forms.Timer 之间的区别。到目前为止一切正常!
  • @michael24B,很高兴我能帮上忙!自从我注意到您对生成多个线程有疑问后,我已经用更多信息更新了我的答案。
  • 感谢您的补充说明。是否可以使用 ThreadPool.QueueUserWorkItem 调用更多函数?我想在同一个线程中调用 LoadExcelData 和 xy 函数。
  • @michael24B,是的,您可以将任何函数添加到线程池,只要它们具有正确的签名(例如void Function(object state))。线程池适用于较短的线程,即您不希望运行超过几秒钟(或稍长)的线程,但如果您认为您的任何线程会运行很长时间,您可能不想将该线程排队,而是手动生成一个线程(如果需要,您仍然可以使用CountDownEventAdd 成员函数)。
  • @michael24B,没问题 :) 更新以反映这一点
【解决方案2】:

父线程将到达连接所有工作线程的 for 循环,并在那里等待,直到所有线程完成(并且可以加入)。如果 GUI 在同一个父线程中运行,则在所有线程完成之前执行不会返回到 GUI,这将是很长时间,因为您已经设置了计时器。尝试在不同的线程中运行 GUI。

编辑: 另外,在您调试时,我会将您的计时器长度设置为更短,以查看它是否真的按照您的预期等待。然后,一旦它正常运行,您就可以将其设置回 20 分钟。

【讨论】:

  • 我应该如何将 GUI 线程与父线程分开并保持功能不变?谢谢 - 我已经在使用较短的间隔(2 分钟)进行调试。
  • 没有看到你的代码,没有。我会尝试将所有与 GUI 相关的内容提取到它自己的方法、类等中,这样你就可以启动一个线程并在程序启动时将其发送出去做 GUI 工作。但是,我之前并没有真正使用过它们,所以我不确定是否有标准的 GUI 架构可供您使用,尤其是在您引入多线程时。
猜你喜欢
  • 1970-01-01
  • 2013-07-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多