Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does or does Task.Wait not start the task if it isn't already running?

According to the Microsoft TPL documentation I read (link) calling the Task.Wait() method will block the current thread until that task finishes (or cancels, or faults). But it also said that if the task in question hadn't started yet, the Wait method will attempt to run it on its own thread by asking the scheduler to reassign it, thus reducing the amount of wastage due to blocking.

I've got a system in which tasks (once running) begin by collecting data by starting other tasks and waiting on their results. These other tasks in turn begin by collecting data from yet other tasks and-so-on-and-so-fort, potentially a few hundred layers deep. I really don't want umpteen tasks blocking and waiting for the one task at the end to finally finish.

However when I tried this out in a test console app, Task.Wait() doesn't seem to start anything at all.

What are the correct incantations for building a sequence of tasks that must all wait on each other with a minimum of wasted cycles? It's sort of like ContinueWith, except starting with the last task in the series...

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
  class Program
  {
    static void Main(string[] args)
    {
      var source = new CancellationTokenSource();
      var token = source.Token;

      // Create a non-running task.
      var task = new Task<string[]>(() => InternalCompute(token), token);

      // Isn't this supposed to start the task?
      task.Wait(CancellationToken.None);

      // I realise this code now won't run until the task finishes,
      // it's here for when I use task.Start() instead of task.Wait().
      Console.WriteLine("Press any key to cancel the process.");
      Console.ReadKey(true);
      source.Cancel();
      Console.WriteLine("Source cancelled...");

      Console.WriteLine("Press any key to quit.");
      Console.ReadKey(true);
    }

    private static string[] InternalCompute(CancellationToken token)
    {
      string[] data;
      try
      {
        data = Compute(token);
      }
      catch (TaskCanceledException ex)
      {
        return null;
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
        return new[] { ex.Message };
      }

      Console.WriteLine("Post-processor starting.");
      for (int i = 0; i < data.Length; i++)
        if (data[i] is null)
          Console.WriteLine($"Null data at {i}.");
        else
          Console.WriteLine($"Valid data at {i}.");
      Console.WriteLine("Post-processor completed.");
      return data;
    }

    /// <summary>
    /// This method stands in for an abstract one to be implemented by plug-in developers.
    /// </summary>
    private static string[] Compute(CancellationToken token)
    {
      var data = new string[10];
      for (int i = 0; i < data.Length; i++)
      {
        token.ThrowIfCancellationRequested();
        Thread.Sleep(250);
        data[i] = i.ToString();
        Console.WriteLine($"Computing item {i + 1}...");
      }
      return data;
    }
  }
}
like image 502
David Rutten Avatar asked Dec 11 '25 07:12

David Rutten


1 Answers

Tasks are generally split into two groups - "cold" tasks and "hot" tasks. "cold" tasks are tasks that have not yet been started and are not meant to run yet. "hot" tasks are tasks that may or may not be currently running but, importantly, if they're not running yet, they may do so at any time. They're meant to be running but haven't yet been assigned the resource (a thread) they need to do so.

What this post is talking about is executing a "hot" task that hasn't otherwise had an opportunity to run. "hot" tasks are created by calling e.g. Task.Run(). They're also e.g. the type of Tasks you'll receive from async methods. new Task(...), on the other hand gives you "cold" tasks. Unless or until you call Start or moral equivalent methods on that task, it remains "cold". It's calling one of those methods explicitly that makes it "hot" instead of "cold".

Generally, you don't want to be working with "cold" tasks at all these days, which is why directly calling a Task constructor is frowned upon. They were really a bad experiment from before they worked out how scheduling should really work. Most modern code doesn't expect to be working with "cold" tasks at all.

The key quote from the above post is this one:

However, if it hasn’t started executing, Wait may be able to pull the target task out of the scheduler to which it was queued and execute it inline on the current thread.

If you've not called Start on the task, it hasn't been queued with a scheduler - so obviously we cannot do what the above says.

like image 170
Damien_The_Unbeliever Avatar answered Dec 13 '25 19:12

Damien_The_Unbeliever



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!