【问题标题】:Thread not run immediately when using more than 4 BackgroundWorker使用超过 4 个 BackgroundWorker 时线程不会立即运行
【发布时间】:2015-03-19 10:54:10
【问题描述】:

我使用多个BackgroundWorker 控件将某些任务作为多线程运行。但是我发现,当使用超过 4 个 BackgroundWoker 时,从第 4 个向前延迟超过 1 秒才能真正执行调用 RunWorkerAsync

可以帮助我如何立即启动所有后台工作人员吗?

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 个内核,它们都在处理一个线程,其他内核必须等待资源间隙才能运行。多线程的谬误之一就是添加大量线程会使事情变得更快,但通常它会使事情变得更慢!
  • @Belogix:Windows 调度相同优先级的线程循环,最后我检查了一个线程的时间量约为 50 毫秒。因此,虽然超过内核数量会稍微延迟线程启动,但不足以解释此处报告的一秒延迟。在初始线程之一被抢占并允许第五个线程运行之前,第五个线程最多必须再等待 50 毫秒。
  • 微软在 BackgroundWorker 类中犯了一个设计错误,没有人在线程池线程是理想的场景中使用它。但他们也非常擅长修复错误,您应该改用 Task 类。您可以使用 TaskCreationOptions.LongRunning 选项来解决您的问题。请注意您的代码是非常虚假的,您确实需要尝试使用您的真实代码。
  • 我正在使用不支持任务的 .Net 3.5。为什么BackgroundWorker是一个设计错误,我很惊讶,因为网上有很多人推荐使用它。

标签: c# .net multithreading backgroundworker


【解决方案1】:

管理用于BackgroundWorker(和其他需求)的线程池线程的ThreadPool 类不会保持无限数量的工作线程准备运行。

您可以使用ThreadPool.SetMinThreads() 方法配置实际的空闲线程数(*)。正如您在案例中看到的那样,当您最初启动程序时,有四个空闲线程准备好立即接受工作。默认空闲线程数取决于与操作系统版本和配置相关的各种因素。

一旦线程池的排队工作项多于为其提供服务的线程,ThreadPool 类不会立即创建新线程。它会等待很短的时间(从您的测试中可以看出,一秒钟),假设它可能其他任务之一可能很快完成并且它将能够重用它线程而不是费尽心思创建另一个线程(这会产生自己的开销,甚至会减慢已经运行的线程的工作速度)。

一般来说,您应该避免覆盖线程池的默认值,因为它们通常会根据您的操作系统版本、硬件等进行正确设置。例如,运行 CPU 绑定的线程数量多于你的机器上有 CPU 内核。让ThreadPool 类决定何时以及如何运行工作线程通常是最好的方法。


(*) 以上内容有点过于简单化了。在较新版本的 .NET 中,最小线程数可能在任何给定时间实际存在,也可能不存在。如果工作项在少于最小数量时排队,ThreadPool 将立即根据需要创建新线程,直到达到最小值。除此之外,它会转向更精细的创建和调度逻辑。

【讨论】:

  • 感谢您的信息!是的,它确实可以正确处理 CPU 密集型任务。但是当我的场景是 IO 绑定时,即我会启动 100 个任务,处理 100 个 IO 通道,但每个都需要很少的 CPU 工作。在这种情况下,低线程池最小线程数是否仍然是一个合理的预设?
  • @KFL:I/O 有点不同。 IOCP 有一个单独的线程池。 IOCP 的设计使您不需要几乎 与未完成的 I/O 操作一样多的线程,因为只有在 I/O 操作实际完成时才会唤醒线程。坦率地说,.NET 中的默认值几乎总是您想要的。从事超大规模实现工作的人可能会发现自定义值有一点帮助,但要涵盖如何实现这一点,它需要的内容比 Stack Overflow 评论中解释的要多得多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-28
  • 2020-04-27
  • 2013-03-28
  • 2019-02-22
  • 2023-04-10
  • 1970-01-01
相关资源
最近更新 更多