我正在开发Windows Form C#程序,该程序每20分钟从共享驱动器读取Excel数据(我使用"计时器")-函数"插入"。由于性能原因,我想同时读取多个Excel文件。出于这个原因,我使用线程。
每个线程都在调用一个函数(LoadExcelData),该函数将数据从Excel读取到ArrayList。我想知道所有线程何时完成(所有excel文件何时加载到ArrayList),以便将此ArrayList插入内部数据库。
我尝试了线程[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:
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线程正在冻结,因为您使用的是System.Windows.Forms.Timer
,它在UI线程上触发计时器勾选的事件;这很有用,因为您不必在tick事件上Invoke
任何内容。调用Join
会阻塞调用线程,在您的情况下,这就是UI线程。
为了避免这种情况(并且由于您不需要Invoke
任何UI元素),您可以将System.Windows.Forms.Timer
更改为System.Timers.Timer
,它在独立于UI线程的线程中运行。如果切换到System.Timers.Timer
,则需要更改代码中的一些语法(例如,Tick
事件改为Elapsed
事件,等等)
还有System.Thread.Timer
和System.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
传递的null委托),或者如果操作系统无法创建线程,如果操作系统不能创建线程,则会出现更大的问题。可能会出现内存耗尽的情况,因为Thread
是一个托管对象,因此会占用内存(与ArrayList
一起),但在任何能够运行.NET框架的系统上(即使在大多数嵌入式系统上),100多个线程(甚至1000多个线程)的内存量都可以忽略不计,因此线程数量不一定是个问题。
查看您的代码,您可能需要考虑使用System.Threading.ThreadPool
和System.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处理线程的调度,以避免在线程数量很大的情况下发生大规模的资源争用。您可以使用其他方法进行阻塞,如Mutex
或Semaphore
,但CountDownEvent
几乎封装了您需要对其他等待对象执行的操作,以及在线程池中的线程上进行连接。
不过,老实说,由于您在多个线程中从Excel文件中读取数据,除非每个线程将文件的全部内容读取到RAM中,然后以这种方式执行操作,否则您可能不会看到性能的大幅提高。具有大量I/O的多线程应用程序通常不会看到巨大的性能提升,除非所述I/O位于注重性能的设备上,或者整个文件的初始输入被读取到RAM中。只是一个旁注,因为你是多线程处理文件。
还应该注意的是,使用System.Threading.ThreadPool
非常适合于只运行几秒钟左右的线程;如果您预计一个线程可能需要更长的时间,那么您应该像现在这样继续生成线程。您仍然可以使用CountDownEvent
,并且不需要像现在这样的线程数组(您可以只使用(new Thread(function)).Start()
语法)。
希望这能帮助
父线程将到达连接所有工作线程的for循环,并在那里等待,直到所有线程都完成(并且可以连接)。如果GUI在同一个父线程中运行,那么在所有线程都完成之前,执行不会返回到GUI,这将是一段很长的时间,因为您已经设置了计时器。尝试在其他线程中运行GUI。
编辑:另外,在调试时,我会把你的计时器长度设置得更短,看看它是否真的像你期望的那样等待。然后,一旦你让它正常工作,你就可以把它设置回20分钟。