2015-03-19 48 views
1

我使用多个BackgroundWorker控件以多线程方式运行某些任务。但是我发现当使用超过4个BackgroundWoker时,从第四个转发延迟超过第二个延迟到从拨打RunWorkerAsync时实际执行。当使用多于4个时,线程不会立即运行BackgroundWorker

可以帮助我如何立即启动所有背景工作?

class TaskLog 
{ 
    public int task_id; 
    public DateTime call_time; 
    public DateTime start_time; 
    public DateTime end_time; 
} 

BackgroundWorker[] bws = new BackgroundWorker[18]; 
int[] tasks = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 
Queue<TaskLog> queueTask; 
TaskLog[] records; 
int task_complete = 999; 

private void button1_Click(object sender, EventArgs e) 
{ 
    if (task_complete < tasks.Length) return; 

    task_complete = 0; 

    records = tasks.Select(t => new TaskLog { task_id = t }).ToArray(); 
    queueTask = new Queue<TaskLog>(records); 

    for (int i = 0; i < bws.Length && queueTask.Count > 0; ++i) 
    { 
     bws[i] = new BackgroundWorker(); 
     bws[i].DoWork += new DoWorkEventHandler(download_vid_work); 
     bws[i].RunWorkerCompleted += new RunWorkerCompletedEventHandler(download_vid_complete); 

     var x = queueTask.Dequeue(); 
     x.call_time = DateTime.Now; 

     bws[i].RunWorkerAsync(x); 
     //Debug.WriteLine("start " + x.task_id); 
    } 
} 

void download_vid_work(object sender, DoWorkEventArgs e) 
{ 
    var record = (TaskLog)e.Argument; 
    record.start_time = DateTime.Now; 
    //Debug.WriteLine("actually start " + record.task_id); 

    Thread.Sleep(10000); // 10s 
    e.Result = record; 
} 

void download_vid_complete(object sender, RunWorkerCompletedEventArgs e) 
{ 
    var record = (TaskLog)e.Result; 
    record.end_time = DateTime.Now; 
    //Debug.WriteLine("complete " + item.ToString()); 

    ++task_complete; 
    if (task_complete == tasks.Length) 
    { 
     Debug.WriteLine("all tasks are completed!"); 
     foreach (var r in records) 
     { 
      Debug.WriteLine("task {0} delay time: {1}", r.task_id, (r.start_time - r.call_time).TotalMilliseconds.ToString("0,0")); 
     } 
    } 
    else if (queueTask.Count > 0) 
    { 
     var bw = (BackgroundWorker)sender; 
     var nextTask = queueTask.Dequeue(); 
     bw.RunWorkerAsync(nextTask); 
     nextTask.call_time = DateTime.Now; 
    } 
} 

下面是运行后的日志结果:

all tasks are completed! 
task 1 delay time: 22 
task 2 delay time: 24 
task 3 delay time: 24 
task 4 delay time: 23 
task 5 delay time: 1,005 
task 6 delay time: 2,002 
task 7 delay time: 3,003 
task 8 delay time: 4,003 
task 9 delay time: 5,004 
task 10 delay time: 6,005 
+4

您不能拥有无限数量的线程并期望它们全部运行。你可能有4个核心,他们都在处理一个线程,其他人不得不等待资源缺口才能运行。多线程的谬误之一就是添加大量的线程会使事情变得更快,通常它会让事情变得更慢! – Belogix 2015-03-19 10:55:14

+0

@Belogix:Windows调度具有相同优先级循环的线程,最后我检查一个线程的量程大约为50ms。因此,尽管超过内核数量会使线程启动延迟一小段时间,但这不足以解释此处报告的一秒延迟。第五个线程必须等待最多50毫秒,然后才能使其中一个初始线程被抢占并允许第五个线程运行。 – 2015-03-19 12:12:54

+1

微软在BackgroundWorker类中犯了一个设计错误,没有人在线程池线程理想的情况下使用它。但他们也很擅长修复他们的错误,您应该使用Task类来代替。您可以使用TaskCreationOptions.LongRunning选项来解决您的问题。请注意,您的代码非常虚假,您需要试用真实的代码。 – 2015-03-19 13:27:38

回答

2

ThreadPool类,管理用于BackgroundWorker(及其他需要)线程池中的线程,不维护工人无限数量的线程准备跑步。

您可以使用ThreadPool.SetMinThreads()方法配置空闲线程的实际数量(*)。正如你可以看到你的情况,当你最初启动你的程序时,有四个空闲线程可以立即接受工作。空闲线程的默认数量取决于与操作系统版本和配置相关的各种事情。

一旦线程池中有更多排队的工作项比有线程要服务它们,ThreadPool类不会立即创建新线程。它等待一段时间(正如你可以从你的测试中看到的那样),假设它可能是其他任务之一可能很快就会完成,它将能够重用该线程而不是去所有创建另一个线程的麻烦都会产生(它会产生自己的开销,甚至会减慢已经运行的线程的工作)。

一般来说,您应该避免重写线程池的默认值,因为它们通常在您的操作系统版本,硬件等设置正确的情况下设置。例如,它将无助于运行更多CPU绑定线程比你机器上的CPU核心要多。让ThreadPool类决定何时以及如何运行工作线程通常是最好的方法。


(*)以上是一些过度简化。在较新版本的.NET中,线程的最小数量在任何给定时间可能实际存在或可能不存在。如果工作项目在少于最小数量时排队,ThreadPool将根据需要立即创建最大数量的新线程。除此之外,它转向更精细的创建和调度逻辑。

相关问题