6

I'm running in a loop and kicking off tasks in the following manner:

var iResult = new List<Task>();
foreach(var i in myCollection)
{
    var task = Task.Factory.StartNew(() =>
                    DoSomething(), TaskCreationOptions.LongRunning);
    task.ContinueWith(m => myResponseHandler(m.Result));
    iResult.Add(task);
}

Within my DoSomething() method, I have a timer:

public static myMsg DoSomething()
{
    var timer = System.Diagnostics.Stopwatch.StartNew();
    DoLongRunningTask(); //If it matters this hits a REST endpoint (https)
    timer.Stop();

    return new myMsg(timer.ElaspedMilliseconds);
}

When I iterate through my list of myMsg's the ElaspedMilliseconds seems to be completely additive - the ElaspedMilliseconds on the first one might be 300, but the last one could be 50000 (50 seconds) - which is actually the approximate time the entire thing takes to run (as measured by another timer).

Prescott
  • 7,312
  • 5
  • 49
  • 70
  • 1
    Updated [my answer](http://stackoverflow.com/a/16259019/200449). It only deceptively looks additive because the ElapsedMilliseconds values are always output in increasing order ONLY - in order of tasks completion (in order of task's duration) which is not the order in which the tasks were launched – Gennady Vanin Геннадий Ванин Apr 28 '13 at 07:55

2 Answers2

3

Edit:

Ooops, I was also first confused.

The problem is that it only looks aditive (accumulative) because the ElapsedTime values are always output in increasing order only.

So, if I have, as in my demo below, in the order of launching:

  • 1st launched task's duration of 10 sec (10000 msec),
  • the 2nd task's duration of 8 sec (8 000 ms),
  • the 3d task's duration of 6 sec (6 000 ms),

then the results appear in output out of initial order - ALWAYS in order of increasing duration of tasks:

  • the 1st in output: the 3d launched task's duration (of 6 sec duration)
  • the 2nd in output: the 2nd launched task's duration (of 8 sec duration)
  • the 3d (last) in output: the 1st launched task's duration (of 10 sec duration)

Here is the output from Console app below:

from DoSomething  6043  
from main  6043  
from DoSomething  8057  
from main  8057  
from DoSomething  10058
from main  10058

And it is obvious why - because the faster task always finishes and output before longer (more time consuming) task.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics;

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {
      var iResult = new List<Task>();
      for (int i=5; i>2; i--)
      {
        int load = i;
        var task = Task.Factory.StartNew(() =>
                        DoSomething(load), TaskCreationOptions.LongRunning);
        //following commented lines do NOT change the behavior in question
        task.ContinueWith(m => Console.WriteLine("from main  "+m.Result));
        //iResult.Add(task);
      }
      Console.ReadLine();
    }

   //public static myMsg DoSomething()
    public static long DoSomething(int load)
    {
      Stopwatch timer = System.Diagnostics.Stopwatch.StartNew();

      //usage of either prev or following 2 lines produce the same results 
      //Stopwatch timer = new Stopwatch();  //instead of prev .StartNew();
      //timer.Start();// instead of prev .StartNew();

      Console.WriteLine("***Before calling  DoLongRunningTask()   " 
               + timer.ElapsedMilliseconds);
      Console.WriteLine("GetHashCode  "+timer.GetHashCode());

      DoLongRunningTask(load); 
      timer.Stop();

      long elapsed = timer.ElapsedMilliseconds;
      Console.WriteLine("from DoSomething  "+ elapsed);

      return elapsed;//return new myMsg(timer.ElaspedMilliseconds);
    }

    public static void DoLongRunningTask(int load)
    {
      Thread.Sleep(2000*load);
      /*******************  another variant of calculation intensive loading
             load = load;
             double result = 0;
             for (int i = 1; i < load*100000; i++)
                   result += Math.Exp(Math.Log(i) );
       */
    }
  }
}
2

One thing that is most likely to happen is that DoLongRunningTask() might not be multithreaded properly, which mean one task run after the first is completed and so on. Each task has its own timer and they are all start somewhere around the same time (or as threads are allocated to the tasks), but the long running task offset them all.

You never have an unlimited thread pool and Tasks handles who get a thread and when.

About LongRunning:

"It's not a specific length per se. If you're generating a lot of tasks, LongRunning is not appropriate for them. If you're generating one or two tasks that will persist for quite some time relative to the lifetime of your application, then LongRunning is something to consider. In general, don't use it unless you find that you really need it. Under the covers, it's going to result in a higher number of threads being used, because its purpose is to allow the ThreadPool to continue to process work items even though one task is running for an extended period of time; if that task were running in a thread from the pool, that thread wouldn't be able to service other tasks. You'd typically only use LongRunning if you found through performance testing that not using it was causing long delays in the processing of other work." - Stephen Toub

LightStriker
  • 19,738
  • 3
  • 23
  • 27
  • Just to elaborate on this, if `DoLongRUnningTask()` locks on a shared piece of data, it's quite possible that the timer *is* correct in being additive, because each one starts, then pauses until the previous one is done. Try replacing it with a simple delaying task and seeing if that gets timed right. – Bobson Apr 26 '13 at 16:40
  • @Bobson , [I am reproducing the same](http://stackoverflow.com/a/16259019/200449) without any locks on anyshared pice of data – Gennady Vanin Геннадий Ванин Apr 28 '13 at 03:19
  • @LightStriker, so what if "`DoLongRunningTask()` might not be multithreaded properly", even if it is reusing the same dedicated thread (what is most probably due to `TaskCreationOptions.LongRunning`)? Even when I explicitly create new instances `Stopwatch timer = new Stopwatch();` or reset the time before using it, the `timer.ElapsedMilliseconds` value is accumulative/incrementing to previous instances – Gennady Vanin Геннадий Ванин Apr 28 '13 at 03:20
  • You don't have unlimited number of threads. Code cannot and will not run in an infinite parallelism. Depending on your computer, on your software, on your OS, on the tasks parameters and how your code is done and compiled, it's not unlikely that the tasks only share one single thread and get resolved one after the other. Each task might wait for the previous one to be completed. One thing you could try is to eliminate the task overhead and run directly threads instead. But if your OS only assign a single thread to your process, you won't see any difference. Edited my answer about LongRunning. – LightStriker Apr 28 '13 at 07:07
  • 1
    @LightStriker , how does this answer, explain or relate to question? – Gennady Vanin Геннадий Ванин Apr 28 '13 at 07:49
  • @ГеннадийВанинНовосибирск Do me, it sounds pretty clear, even more if you read Stephen Toub's text about LongRunning. What exactly you don't understand? – LightStriker Apr 28 '13 at 13:43