0

I'm making service for watch on some controller data and if it changing then I write it to DB. Seems simple. previosly I realized the same with Delphi, but now I am on C# (.Net 4.5). Now service works good with 100 tasks, but eats about 7-8% of CPU time. My Delphi service eats about 0%. How can I reduce time which service eat from CPU? P.S.: each task has own nstance of class to connect and insert into DB and work with local copy of data.

int TagCnt = DataCtrl.TagList.Count;                     
    stopExec = false;
    if (TagCnt != 0)
    {                
        tasks = new Task[TagCnt];                
        for (int i = 0; i <= TagCnt - 1; i++)
        {                    
            int TempID = i;
            tasks[TempID] = Task.Run(async () => // make threads for parallel read-write tasks // async
            {                                            
                Random rand = new Random();
                TimeSpan delay = TimeSpan.FromMilliseconds(rand.Next(1000, 1500))                                              
                try
                {
                    while (!stopExec)
                    {                               
                    cToken.ThrowIfCancellationRequested();                          
                    //do basic job here
                    await Task.Delay(delay, cToken);
                    }//while end                            
                }
                catch (...)
                {
                ...
                }                                            
            }, cToken);                   
        }
sad1002
  • 29
  • 4
  • 4
    You're initiating a 100 threads, each one in a `while` loop which pools a flag. Why do you expect your CPU % to be zero? – Yuval Itzchakov Feb 19 '15 at 13:08
  • 1
    This should indeed have near 0% consumption since all "threads" sleep ~99.999% of the time. Profile the app. What's in the code not shown here? Are you consuming CPU *there*? – usr Feb 19 '15 at 13:16
  • Why do you have the `while (!stopExec)` there? You've already got a cancellation token, just use that. In any case, just run the application in a profiler, it should show you quite nicely where the CPU is spending its time. Also, is that 7-8% average, or peak? It might simply be the garbage collector, for example. And of course, `Task.Delay` is actually using a system timer, the sleep you had in the Delphi code is less work (all the work gets thrown on the OS, basically - and it's thread scheduler is a bit more capable than a hundred timers you create over and over again, of course). – Luaan Feb 19 '15 at 13:22
  • 1
    @Shelby115 This is off topic on code review. Considering that some code are stub and It has a bug. – bhathiya-perera Feb 19 '15 at 14:02
  • Really? I've seen quite a few questions just like this on CodeReview never closed? Guess I'll re-read the help center :P – Shelby115 Feb 19 '15 at 14:14

4 Answers4

0

Recently I've been facing a similar conundrum and managed to solve the erratic CPU usage by using a set of dedicated long-running tasks to carry out the asynchronous work in my app like so:

    Dim NumThreads As Integer = 10
    Dim CanTokSrc As New CancellationTokenSource
    Dim LongRunningTasks As Task() = New Task(NumThreads) {}
    Dim i As Integer
    Do Until i = LongRunningTasks.Count
        LongRunningTasks(i) = Task.Factory.StartNew(Sub()
                                                        Do Until CanTokSrc.IsCancellationRequested
                                                            'DO WORK HERE
                                                        Loop
                                                    End Sub, CanTokSrc.Token, TaskCreationOptions.LongRunning)
        i = i + 1
    Loop

This image shows the difference it made in CPU usage for the same workload (shown after 9am).

So I think bypassing the thread pool by using dedicated/ long running tasks like above could improve CPU utilization in some cases. It certainly did in mine :-)

Dĵ ΝιΓΞΗΛψΚ
  • 5,068
  • 3
  • 13
  • 26
  • Yes, I tried this technics too. But work with starting by timer seems for flexible and accurate. Also I'm facing one more problem (I think that TPL parallel.for and tasks factory runs only one thread for each "i", but it's not true...) Of course it highly optimized. But unfortunately I use "i" in my output to DB. So I should go to usual threads or thread pool. – sad1002 Feb 22 '15 at 10:36
0

I moved to timer instructions because it's a windows service. Every event on timer load is about 7-10% and between is 0%. I tried to apply tasks, ThreadSchedule - they seems more heavy.

 private void OnReadTimer(object source, ElapsedEventArgs e) //check states on timer
    {
        int TagCnt = DataCtrl.TagList.Count;
        po.MaxDegreeOfParallelism = DataCtrl.TagList.Count;
        // string ss = "tags=" + TagCnt;
        //int TempID;
        Random rand = new Random();
        try
        {
            if (TagCnt != 0)
            {                    
                ParallelLoopResult loopResult = Parallel.For(0, TagCnt - 1, po, (i, loopState) =>
                {
                    po.CancellationToken.ThrowIfCancellationRequested();
                    int TempID = i;
                    Thread.Sleep(rand.Next(100, 200));
                    int ID = 0;
                    bool State = false;
                    long WT = 0;
                    int ParID = 0;
                    bool Save = false;                        
                    ReadStates(TempID, out ID, out State, out WT, out ParID, out Save);
                    lock (locker)
                    {
                        if (Save) WriteState(ID, State, WT, ParID);
                    }
                });
            }


        }
        catch (TaskCanceledException)
        {
        }
        catch (System.NullReferenceException eNullRef)
        {
            AddLog("Error:" + eNullRef);
        }
        catch (System.ArgumentOutOfRangeException e0)
        {
            AddLog("Error:" + e0);
        }
        catch (Exception e1)
        {
            //AddLog("Error while processing data: " + e1);
        }
    }
sad1002
  • 29
  • 4
0

I moved to basic threads with infinite loops inside. It gets endless threads for my needs. No heavy recreating/restarting and so on. Now it works nice like Delphi service, but more comfortable job with data and DB. I starts threads with this procedure from lambda new thread()=>:

        void RWDeviceState(int i)
    {
        try
        {
            int TempID = i;
            long StartTime;
            long NextTime;
            long Period = 3000;
            int ID = 0;
            bool State = false;
            long WT = 0;
            int ParID = 0;
            bool Save = false;                
            while (ExecutionAllowed)
            {
                Save = false;
                ReadStates(TempID, out ID, out State, out WT, out ParID, out Save);
                lock (locker)
                {
                    if (Save) WriteState(ID, State, WT, ParID);
                }
                StartTime = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
                NextTime = StartTime + Period;
                while (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond < NextTime && ExecutionAllowed)
                {
                    Thread.Sleep(40);
                }
            }
sad1002
  • 29
  • 4
-2

There are two particular techniques that will help reduce CPU usage in long loop waits. One, is to use the threading sleep method. This is good for example in standalone applications, less in windows services.

In a service, for the second, you should be using timers. These fire at regular intervals, so in between the intervals the CPU is not solicited.

JP Lizotte
  • 11
  • 2
  • I tried timer event technique but service eat 7-8% in average. – sad1002 Feb 19 '15 at 13:38
  • I checked version with timer, it starts parallel.for(,,=>{}); with maxDegreeofParallelism=2. Also I added Thread.Sleep(rand.Next(20,50)) to move apart work of "threads". Now average cpu load from this service is 0%. This what I want to do. Of course I was needed to replace Stopwatch (for my needs) basic substraction. – sad1002 Feb 20 '15 at 07:40
  • I was too fast with previous message. Timer with interval 3s makes average load about 3-4% of max CPU. May be work with several controllers and connection to MSSQL should eat something. It's not free... – sad1002 Mar 02 '15 at 13:05