Console app takes a long time to exit after finishing work

流过昼夜 提交于 2019-12-24 00:42:20

问题


I have a console app that queries a database and then posts some records to a REST API in a loop (the api does not support batch posting, so I have to loop through each record and post individually, if its relevant). The database access is fast and no issue and so is the api post loop according to the timer I've put in place, however the app itself takes a long time to exit after the work is done.

This started happening after I introduced Parallel.Foreach to speed up the posting. Before using a non-parallel loop, posting 1000 records took on average ~10mins, but the app would return and exit immediately when it was done (as expected). With the parallel loop in place, this, according to the Stopwatch timer I'm using, is reduced to an average of ~44secs, however the app doesn't exit until around 2 minutes have passed - ~1min15sec after all work has completed.

The app isn't doing anything 'extra'. It enters main, main calls a method to retrieve some records from a database (1-2 secs), forwards 1000 of those records to another method that loops through them and posts each to the api, then exits. Except, it doesn't exit immediately in this case, for some reason.

I put a stopwatch timer in main immediately before the call to the posting method and log the time immediately after the method returns, and the timer aligns with the timer inside the method, averaging ~46 seconds. So the delay is happening after the posting method has returned but before the main function exits, but there is nothing defined for it to do at this point. Debugging didn't show anything out of the ordinary. Is this a de-allocation issue related to all the objects spawned by the parallel loop that are 'hanging around'?

This happens regardless of whether I am running with a debugger attached or executing the binary directly when built for release (so not a detaching delay issue). I've looked at other SO questions like this but their approaches have not made a difference. Any input would be appreciated.

Code of the posting function:

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;
    }
}

回答1:


Yes it would be freeing up the memory. Your thread will be using up memory and you can limit this by using ParallelOptions.MaxDegreeOfParallelism Property, which will then slow down the query, of course, and you need to manage the memory deallocation - if you want to reduce the time taken to exit the application.

You can dispose of your tasks, if scalability is an issue and you use up too much memory, or wish to clean up resources as you go. As the Parallel class extends the Task class.

Although, calling the garbage collector may be a more foolproof design for you.

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

To reduce the garbage collection at the end of the run, you can implement your own garbage collection, as shown in this answer

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

Where you can periodically manually call for garbage collection.

Also helpful:
Possible memoryleak in ConcurrentBag?

This answer gives examples of how to use MaxDegreeOfParallelism

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

Managing this is important if you wish to scale your application, so as to avoid memory leaks and OutOfMemoryException.



来源:https://stackoverflow.com/questions/45298554/console-app-takes-a-long-time-to-exit-after-finishing-work

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!