【问题标题】:Nested Threads (Tasks) Timing Out Prematurely嵌套线程(任务)过早超时
【发布时间】:2016-04-27 18:52:00
【问题描述】:

我有以下代码,我认为它的作用并不重要,但我的行为很奇怪。

当我在单独的线程上运行几个月时,它运行良好(如下所示),但是当我多线程几年(取消注释任务)时,它每次都会超时。超时设置为月 5 分钟/年 20 分钟,并在一分钟内超时。

这种行为是否有已知原因?我错过了一些简单的东西吗?

    public List<PotentialBillingYearItem> GeneratePotentialBillingByYear()
    {
        var years = new List<PotentialBillingYearItem>();
        //var tasks = new List<Task>();
        var startYear = new DateTime(DateTime.Today.Year - 10, 1, 1);
        var range = new DateRange(startYear, DateTime.Today.LastDayOfMonth());

        for (var i = range.Start; i <= range.End; i = i.AddYears(1))
        {
            var yearDate = i;
            //tasks.Add(Task.Run(() =>
            //{
                years.Add(new PotentialBillingYearItem
                {
                    Total = GeneratePotentialBillingMonths(new PotentialBillingParameters { Year = yearDate.Year }).Average(s => s.Total),
                    Date = yearDate
                });
            //}));
        }

        //Task.WaitAll(tasks.ToArray(), TimeSpan.FromMinutes(20));

        return years;
    }

    public List<PotentialBillingItem> GeneratePotentialBillingMonths(PotentialBillingParameters Parameters)
    {
        var items = new List<PotentialBillingItem>();
        var tasks = new List<Task>();
        var year = new DateTime(Parameters.Year, 1, 1);
        var range = new DateRange(year, year.LastDayOfYear());

        range.Start = range.Start == range.End ? DateTime.Now.FirstDayOfYear() : range.Start.FirstDayOfMonth();

        if (range.End > DateTime.Today) range.End = DateTime.Today.LastDayOfMonth();

        for (var i = range.Start; i <= range.End; i = i.AddMonths(1))
        {
            var firstDayOfMonth = i;
            var lastDayOfMonth = i.LastDayOfMonth();
            var monthRange = new DateRange(firstDayOfMonth, lastDayOfMonth);
            tasks.Add(Task.Run(() =>
            {
                using (var db = new AlbionConnection())
                {

                    var invoices = GetInvoices(lastDayOfMonth);

                    var timeslipSets = GetTimeslipSets();

                    var item = new PotentialBillingItem
                    {
                        Date = firstDayOfMonth,
                        PostedInvoices = CalculateInvoiceTotals(invoices.Where(w => w.post_date <= lastDayOfMonth), monthRange),
                        UnpostedInvoices = CalculateInvoiceTotals(invoices.Where(w => w.post_date == null || w.post_date > lastDayOfMonth), monthRange),
                        OutstandingDrafts = CalculateOutstandingDraftTotals(timeslipSets)
                    };

                    items.Add(item);
                }
            }));
        }

        Task.WaitAll(tasks.ToArray(), TimeSpan.FromMinutes(5));

        return items;
    }

【问题讨论】:

  • 1) 您真的不想在无限数量的线程访问您的数据库的情况下这样做。使用Parallel. 并设置最大并发度; 2) List&lt;&gt; 不是线程安全的。另请参阅 PLINQ:msdn.microsoft.com/en-us/library/dd997425(v=vs.110).aspx
  • 很好的电话List&lt;&gt;,我从没想过。

标签: c# .net multithreading task


【解决方案1】:

您可以考虑预先分配更多的线程池线程。线程池分配新线程的速度非常慢。将线程池线程的最小数量设置为 2.5k 的任务下面的代码只需 10 秒(理论上的最小值)即可运行,但注释掉 SetMinThreads 会使它花费超过 1:30 秒。

static void Main(string[] args)
{
    ThreadPool.SetMinThreads(2500, 10);
    Stopwatch sw = Stopwatch.StartNew();
    RunTasksOutter(10);
    sw.Stop();
    Console.WriteLine($"Finished in {sw.Elapsed}");
}

public static void RunTasksOutter(int num) => Task.WaitAll(Enumerable.Range(0, num).Select(x => Task.Run(() => RunTasksInner(10))).ToArray());
public static void RunTasksInner(int num) => Task.WaitAll(Enumerable.Range(0, num).Select(x => Task.Run(() => Thread.Sleep(10000))).ToArray());

您也可能用完了线程池线程。 Per:https://msdn.microsoft.com/en-us/library/0ka9477y(v=vs.110).aspx 不使用线程池(由任务使用)的时间之一是:

您的任务会导致线程长时间阻塞。线程池具有最大线程数,因此大量阻塞的线程池线程可能会阻止任务启动。

既然 IO 正在这些线程上完成,是否可以考虑用异步代码替换它们或使用 LongRunning 选项启动它们? https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcreationoptions(v=vs.110).aspx

【讨论】:

  • 但为什么会在一分钟内超时?
  • 线程池在创建任何新线程之前增加了 500 毫秒的延迟。池仅以 8 个线程开始。文档特别指出:您需要在很短的时间内启动多个线程,以执行持续一秒或更长时间的任务。作为其线程管理策略的一部分,线程池在创建线程之前会延迟。因此,当多个任务在短时间内排队时,在所有任务启动之前可能会有很大的延迟。
  • 我明白这一点,但是当它点击GeneratePotentialBillingByYear() 时,它会在大约 32 秒后进入函数失败。
  • 我错过了你问题的那一部分。由于您的子任务生成器没有抛出异常,我不知道:-/
  • Ps 使用 Task.Run 时,您对 years.add 的调用不是线程安全的
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-24
  • 2016-07-06
  • 1970-01-01
  • 2016-02-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多