【问题标题】:Console app takes a long time to exit after finishing work控制台应用程序在完成工作后需要很长时间才能退出
【发布时间】:2017-12-31 02:23:53
【问题描述】:

我有一个控制台应用程序,它查询数据库,然后在循环中将一些记录发布到 REST API(该 api 不支持批量发布,因此我必须遍历每条记录并单独发布,如果相关)。数据库访问速度很快,没有问题,根据我设置的计时器,api post 循环也是如此,但是应用程序本身在工作完成后需要很长时间才能退出。

这在我介绍Parallel.Foreach 以加快发布速度后开始发生。在使用非并行循环之前,发布 1000 条记录平均需要大约 10 分钟,但应用程序会在完成后立即返回并退出(如预期的那样)。在并行循环到位后,根据我使用的Stopwatch 计时器,这将减少到平均约 44 秒,但是应用程序直到大约 2 分钟后才会退出 - 在所有工作完成后约 1 分 15 秒完成。

该应用没有做任何“额外”的事情。它进入mainmain 调用一个方法从数据库中检索一些记录(1-2 秒),将这些记录中的 1000 条转发到另一个方法,该方法循环遍历它们并将每个记录发布到 api,然后退出。除了,由于某种原因,在这种情况下它不会立即退出。

我在调用发布方法之前在main 中放置了一个stopwatch 计时器,并在方法返回后立即记录时间,并且计时器与方法内部的计时器对齐,平均约46 秒。所以延迟发生在发布方法返回之后但在main 函数退出之前,但此时没有定义任何事情要做。调试没有显示任何异常。这是一个与并行循环产生的所有“徘徊”的对象相关的取消分配问题吗?

无论我是在附加调试器的情况下运行还是在为发布而构建时直接执行二进制文件,都会发生这种情况(因此不是分离延迟问题)。我看过其他类似这样的 SO 问题,但他们的方法没有产生任何影响。任何意见将不胜感激。

发帖功能代码:

public ProcessingState PostClockingRecordBatchParallel(List<ClockingEvent> batch, int tokenExpiryTolerance)
{
    log.Info($"Attempting to post batch of {batch.Count.ToString()} clocking records to API with an auth token expiry tolerance of {tokenExpiryTolerance} seconds");
    try
    {
        ProcessingState state = new ProcessingState() { PendingRecords = batch };
        List<ClockingEvent> successfulRecords = new List<ClockingEvent>();
        Stopwatch timer = new Stopwatch();

        ServicePointManager.UseNagleAlgorithm = false; //Performance optimization related to RestSharp lib
        authToken = Authenticate();

        timer.Start();
        Parallel.ForEach(state.PendingRecords, pr =>
        {
             successfulRecords.Add(PostClockingRecord(pr, tokenExpiryTolerance));
        });
        //Prior non-parallel version
        //state.PendingRecords.ForEach(pr => 
        //{
        //    successfulRecords.Add(PostClockingRecord(pr, tokenExpiryTolerance));
        //});


        state.PendingRecords        = state.PendingRecords.Except(successfulRecords).ToList();
        state.LastSuccessfulRecord  = successfulRecords.OrderBy(r => r.EventID).Last().EventID;

         log.Info($"PostClockingRecordBatchParallel - Time elapsed: {new TimeSpan(timer.ElapsedTicks).ToString()}");
         return state;
    }
    catch (Exception ex)
    {
            log.Fatal($"Failed to post records to API (exception encountered: {ex}).");
         throw;
    }
}

【问题讨论】:

    标签: c# rest console-application restsharp


    【解决方案1】:

    是的,它会释放内存。您的线程将耗尽内存,您可以通过使用ParallelOptions.MaxDegreeOfParallelism Property 来限制这一点,这当然会减慢查询速度,并且您需要管理内存释放 - 如果您想减少退出应用程序所需的时间.

    您可以dispose of your tasks,如果可伸缩性是一个问题并且您使用了太多内存,或者希望随时清理资源。由于 Parallel class 扩展了 Task 类。

    虽然,调用垃圾收集器对你来说可能是一个更万无一失的设计。

    How can I free-up memory used by a Parallel.Task?

    为了减少运行结束时的垃圾回收,可以实现自己的垃圾回收,如this answer所示

    Action allCollect = () =>
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();
            };
    

    您可以定期手动调用垃圾收集。

    也有帮助:
    Possible memoryleak in ConcurrentBag?

    This answer 举例说明如何使用MaxDegreeOfParallelism

    ParallelOptions.MaximumDegreeOfParallelism = 1: use one full CPU (which will be a percentage of your OS CPU)
    

    如果您希望扩展您的应用程序,管理这一点很重要,以避免内存泄漏和OutOfMemoryException

    【讨论】:

    • 谢谢,正是我想要的!
    猜你喜欢
    • 2018-02-07
    • 2016-07-30
    • 2013-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多