0

im trying to figuring out how C# manages threads.

I just started up new Web Api project. I packed it to new Thread:

public class Program                                                                       
{                                                                                          
    public static void Main(string[] args)                                                 
    {                                                                                      
        var thread = new Thread(() => CreateHostBuilder(args).Build().Run());              
        thread.Start();                                                                    
    }                                                                                      
                                                                                           
    public static IHostBuilder CreateHostBuilder(string[] args) =>                         
        Host.CreateDefaultBuilder(args)                                                    
            // DEFAULT STARTUP HERE
            .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); 
}          

And i added my custom controller to it:

[ApiController]                                      
[Route("[controller]")]                              
public class ValueController : ControllerBase        
{                                                    
    [HttpGet("fast")]                                
    public async Task<ActionResult<int[]>> Fast()    
    {                                                
        var task = new Task<int[]>(() =>             
        {                                            
            return new[] {1, 2, 3};                  
        });                                          
        task.Start();                                
                                                     
                                                     
        return Ok(await task);                       
    }                                                
                                                     
    [HttpGet("infinite")]                            
    public async Task<ActionResult<int[]>> Infinite()
    {                                                
        var task = new Task<int>(() =>               
        {                                            
            while (true)                             
            {                                        
            }                                        
                                                     
            return 0;                                
        });                                          
        task.Start();                                
                                                     
        return Ok(await task);                       
                                                     
    }                                                
}                                                    

First, i run https://localhost:5001/value/infinite endpoint to have one task that never ends in the background.

Then i started to spam my app with https://localhost:5001/value/infinite endpoint.

As you can see in debugger:

First run of endpoint:

enter image description here

Second:

enter image description here

The whole ASP .NET app work on the same thread that with number @13272.

My question is, how .NET manage Tasks in one thread when it is possible to run multiple tasks when there are infinite tasks running in the background?

michasaucer
  • 4,562
  • 9
  • 40
  • 91
  • Did you forget a `thread.Join()`? Though you could probably do without the thread and call `CreateHostBuilder(args).Build().Run()` directly –  Nov 16 '20 at 08:27
  • One of the motives for introducing the `Task` and `Task` abstractions was to allow developers to concentrate on *doing the work necessary*, rather than having to expend time thinking about the *mechanism* of threads. – Damien_The_Unbeliever Nov 16 '20 at 08:29
  • @Damien_The_Unbeliever i think its good to know what happening inside thread and tasks – michasaucer Nov 16 '20 at 08:32
  • 1
    I am not sure what i am meant to be looking at (what the images are trying to show) and what information is you want to know. – TheGeneral Nov 16 '20 at 08:38
  • @The General how is it possibile to compute multiple Tasks im one Thread while one Task od running on the background with Infinitive loop? – michasaucer Nov 16 '20 at 08:40
  • First rule is tasks aren't threads. Also in all likelihood this code in Fast would run synchronously – TheGeneral Nov 16 '20 at 08:43
  • I know that tasks arent threads but how it correspond to each other ? Is Thread manages tasks or `Tasks` are managed in lower level? Or tasks and threads not correspond at all and its completly diffrent "structure"? – michasaucer Nov 16 '20 at 08:47
  • 2
    https://stackoverflow.com/questions/13429129/task-vs-thread-differences – TheGeneral Nov 16 '20 at 08:49
  • 1
    You may find these questions interesting: [C# - ThreadPool vs Tasks](https://stackoverflow.com/questions/1774670/c-sharp-threadpool-vs-tasks) and [What is the conceptual difference between SynchronizationContext and TaskScheduler](https://stackoverflow.com/questions/9580061/what-is-the-conceptual-difference-between-synchronizationcontext-and-taskschedul) – Theodor Zoulias Nov 16 '20 at 09:32
  • @michasaucer why are you asking? What *actual* problem are you trying to solve and why do you care about specific threads? ASP.NET (and Core) always used different threads to serve each request. Each request was always handled by a thread that came from a threadpool, even in .NET 1.0. CPU-bound tasks work the same - the task isn't a thread, it's a work that's submitted to a threadpool for execution by one of the threadpool threads – Panagiotis Kanavos Nov 16 '20 at 10:13
  • 1
    `The whole ASP .NET app work on the same thread that with number @13272` that's wrong. Perhaps the *main thread does, but each request is processed by a separate worker thread. It would be impossible to serve 1000s of concurrent requests otherwise. – Panagiotis Kanavos Nov 16 '20 at 10:14
  • @PanagiotisKanavos "but each request is processed by a separate worker thread" - that's not true either. That won't scale at all. Many requests can be processed by small amount of threads, if asynchronous IO is utilized propertly. – Evk Nov 16 '20 at 11:06
  • 1
    @Evk I didn't say a new thread, I said a different thread `that came from a threadpool,`. I repeat the word `threadpool` three times – Panagiotis Kanavos Nov 16 '20 at 11:09

2 Answers2

1

The concept of running multiple user mode threads on a single OS thread is known as green threads or possibly fibres. Green threads are not used by .Net. While fibers can be used, it is rare, mostly considered obsolete, and not relevant in this case.

In .Net each running task is run by a managed thread, that is backed by a OS thread. However, calling task.Start() does not mean the task is running, it only marks the task as pending, i.e. available to be run when there are available resources.

It is perfectly possible that the query completes before the task is started, and the same thread is reused for the task.

In your example there is also the webserver component to take into consideration. I'm not very familiar with asp.Net, but I would expect that hanging queries would eventually be terminated by the runtime.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • 1
    Since .NET Framework 2.0, a CRL host can use fibers, SQL Server did ([source](http://community.bartdesmet.net/blogs/bart/archive/category/45.aspx)). Thus, Microsoft marked `AppDomain.GetCurrentThreadId` obsolete in most versions ([source](https://docs.microsoft.com/en-us/dotnet/api/system.appdomain.getcurrentthreadid?view=netframework-2.0)). Also .NET can recycle managed thread ids ([source](https://web.archive.org/web/20140425185627/http://blogs.msdn.com/b/visualizeparallel/archive/2012/04/10/how-concurrency-visualizer-represents-recycled-thread-ids.aspx)). *All irrelevant in this case*. – Theraot Nov 16 '20 at 10:25
  • @Theraot SQL Server did and the experience was suboptimal. It's only recommended to use fibers now if instructed to do so by Microsoft support. Fibers require syncrhonization at the application level or very careful coding to avoid blocking or even long-running CPU-bound code – Panagiotis Kanavos Nov 16 '20 at 11:11
0

Here's what happens when you run your program.

  1. The O/S loads the .exe image and locates its main entry point. This is an unmanaged endpoint that you, as a c# programmer, never see.

  2. The main entry point executes the .NET bootstrapper; iloads the .NET runtime, validates your .exe, loads dependent assemblies, establishes the heap and garbage collection thread, and the thread pool. It then calls the Main entry point in your c# application.

  3. The Main method creates another thread, starts it, and returns control to the bootstrapper. At this point your program is more or less finished.

  4. The thread that you started is not a background thread, so it keeps your application open.

  5. Your thread calls the Run extension method of IHost. According to the documentation

Runs an application and block the calling thread until host shutdown.

Since the thread is blocked, it will continue to exist until the host is shut down. That is where you get the @13272 thread.

I don't know much about internals here, but I suspect this is what your IHost ends up doing:

  1. Register TCP/IP listeners with the O/S, e.g. establish callbacks.
  2. When traffic arrives, the O/S invokes the callback.
  3. The callback is handled by the host, which instantiates your controller and invokes the action method asynchronously.
  4. After the action method "returns" (the action method either completed or uses await to yield control), it checks whether the task is completed. If it is not, the task for its continuation is placed on the thread pool, to be executed in due course.

All the stuff that the host does could happen on any thread, which is why those IDs keep changing.

John Wu
  • 50,556
  • 8
  • 44
  • 80