Pergunta

static void Main(string[] args)
{
    // do async method for each stock in the list
    Task<IEnumerable<string>> symbols = Helper.getStockSymbols("amex", 0);
    List<Task> tasks = new List<Task>();

    try
    {
        for (int i = 0; i < symbols.Result.Count(); i++)
        {
            if (i < symbols.Result.Count())
            {
                string symbol = symbols.Result.ElementAtOrDefault(i);
                Task t = Task.Run(() => getCalculationsDataAsync(symbol, "amex"));
                tasks.Add(t);
                Task e = t.ContinueWith((d) => getThreadStatus(tasks));
            }
        }

        // don't exit until they choose to
        while (args.FirstOrDefault() != "exit")
        {
            // do nothing

        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

public static void getThreadStatus(List<Task> taskList)
{
    int count = 0;

    foreach (Task t in taskList)
    {
        if (t.Status == TaskStatus.Running)
        {
            count += 1;
        }
    }

    Console.WriteLine(count + " threads are running.");
}

public static async Task getCalculationsDataAsync(string symbol, string market)
{
    // do calculation here
}

What I'm trying to do in my code is run a new task for each stock in my list and run them all at the same time. I have a 4 core processor and I believe that means I can only run 4 tasks at once. I tried testing how many tasks were running by inserting the continuewith method that you see in my code that will tell me how many tasks are running. When I run this code, it tells me that 0 tasks are running so my questions are:

  1. How can I complete my objective by running these tasks at the same exact time?
  2. Why is it telling me that 0 tasks are running? I can only assume this is because the current task is finished and it hasn't started a new one yet if the tasks are running one after the other.
Foi útil?

Solução

I am not sure why you are seeing no task running. Are you sure your Task.Run() is being hit? That is to say is i < symbols.Result.Count() satisfied?

Regardless of the above, let us try and achieve what you want. Firstly, no, it is not correct to say that because your machine has four physical cores/'threads', that you can use a maximum of four Threads. These are not the same things. A Google on this subject will bring a plethora of information your way about this.

Basically starting a thread the way you have will start background threads on a thread pool, and this thread pool can hold/govern numerous threads see Threading in C# by J. Albahari for more information. Whenever you start a thread, a few hundred microseconds are spent organizing such things as a fresh private local variable stack. Each thread also consumes (by default) around 1 MB of memory. The thread pool cuts these overheads by sharing and recycling threads, allowing multithreading to be applied at a very granular level without a performance penalty. This is useful when leveraging multicore processors to execute computationally intensive code in parallel in “divide-and-conquer” style.

The thread pool also keeps a lid on the total number of worker threads it will run simultaneously. Too many active threads throttle the operating system with administrative burden and render CPU caches ineffective. Once a limit is reached, jobs queue up and start only when another finishes.

Okay, now for your code. Let us say we have a list of stock types

List<string> types = new List<string>() { "AMEX", "AMEC", "BP" };

To dispatch multiple threads to do for for each of these (not using async/await), you could do something like

foreach (string t in types)
{
    Task.Factory.StartNew(() => DoSomeCalculationForType(t));
}

This will fire of three background thread pool threads and is non-blocking, so this code will return to caller almost immediately.

If you want to set up post processing you can do this via continuations and continuation chaining. This is all described in the Albahari link I provided above.

I hope this helps.

--

Edit. to address comments:

Beginning with the .NET Framework version 4, the default size of the thread pool for a process depends on several factors, such as the size of the virtual address space. A process can call the GetMaxThreads method to determine the number of threads.

However, there's something else at play: the thread pool doesn't immediately create new threads in all situations. In order to cope with bursts of small tasks, it limits how quickly it creates new threads. IIRC, it will create one thread every 0.5 seconds if there are outstanding tasks, up to the maximum number of threads. I can't immediately see that figure documented though, so it may well change. I strongly suspect that's what you're seeing though. Try queuing a lot of items and then monitor the number of threads over time.

Basically let the thread pool dispatch what it wants, its optimizer will do the best job for your circumstance.

Outras dicas

I think the problem is that the tasks have a status of Running either very briefly or can skip that status all together and go straight from WaitingForActivation to RanToCompletion.

I modified your program slightly and i can see the tasks starting and completing but they don't appear to be in the Running state whenever I check.

static void Main(string[] args)
{
    // do async method for each stock in the list
    Task<IEnumerable<string>> symbols = Task.FromResult(Enumerable.Range(1, 5).Select (e => e.ToString()));

    List<Task> tasks = new List<Task>();

    try
    {
        for (int i = 0; i < symbols.Result.Count(); i++)
        {
             string symbol = symbols.Result.ElementAtOrDefault(i);
             Task t = getCalculationsDataAsync(symbol, "amex", tasks);
             tasks.Add(t);           
        }               

        Console.WriteLine("Tasks Count:"+ tasks.Count());                       
        Task.WaitAll(tasks.ToArray());
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

public static void getThreadStatus(List<Task> taskList)
{
    int count = 0;

    foreach (Task t in taskList)
    {
        Console.WriteLine("Status " + t.Status);
        if (t.Status == TaskStatus.Running)
        {
            count += 1;
            Console.WriteLine("A task is running");
        }
    }

    //Console.WriteLine(count + " threads are running.");
}

public static async Task getCalculationsDataAsync(string symbol, string market, List<Task> tasks)
{
    Console.WriteLine("Starting task");
    var delay = new Random((int)DateTime.Now.Ticks).Next(5000);
    Console.WriteLine("Delay:" + delay);
    await Task.Delay(delay);
    Console.WriteLine("Finished task");
    getThreadStatus(tasks);
}

Output

Starting task
Delay:1784
Starting task
Delay:2906
Starting task
Delay:2906
Starting task
Delay:2906
Starting task
Delay:2906
Tasks Count:5
Finished task
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Finished task
Finished task
Finished task
Status RanToCompletion
Status RanToCompletion
Status WaitingForActivation
Status WaitingForActivation
Status RanToCompletion
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Status WaitingForActivation
Finished task
Status RanToCompletion
Status RanToCompletion
Status RanToCompletion
Status RanToCompletion
Status WaitingForActivation

As I describe on my blog, there are two kinds of tasks: Delegate Tasks and Promise Tasks. Only Delegate Tasks ever actually run. Promise Tasks only complete. The task returned by Task.Run is a Promise Task, not a Delegate Task; this is necessary so that Task.Run can understand async code and only complete when the async code is completed.

So, checking for TaskStatus.Running is not going to work the way you want. Instead, you can create a counter:

private static int _count;
static void Main(string[] args)
{
  ...
    for (int i = 0; i < symbols.Result.Count(); i++)
    {
      if (i < symbols.Result.Count())
      {
        string symbol = symbols.Result.ElementAtOrDefault(i);
        Task t = Task.Run(() => getCalculationsDataWithCountAsync(symbol, "amex"));
        tasks.Add(t);
      }
    }
  ...
}

public static async Task getCalculationsDataWithCountAsync(string symbol, string market)
{
  Console.WriteLine(Interlocked.Increment(ref _count) + " threads are running.");
  try
  {
    await getCalculationsDataAsync(symbol, market);
  }
  finally
  {
    Console.WriteLine(Interlocked.Decrement(ref _count) + " threads are running.");
  }
}

Note that I used a separate async "wrapper" method instead of messing with ContinueWith. Code using await instead of ContinueWith more correctly handles a number of edge cases, and IMO is clearer to read.

Also, remember that async and await free up threads while they're "awaiting." So, you can potentially have hundreds of (asynchronous) tasks going simultaneously; this does not imply that there are that many threads.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top