1

Coming from python background where we have:

  1. Multiprocessing- to run multiple tasks in parallel on the processors - ideal for cpu bound tasks.

  2. Multi threading- to run multiple tasks in parallel via threads within a single processor. Ideal for io bound tasks. Io bound only because of presence of GIL which results in only 1 thread being able to use the processor at any point in time.

  3. Async await- to do cooperative programming for io bound tasks.

Using python code I can achieve either of the above.

In the case of c# I am learning that: We can either start threads explicitly or use concept of async await and Task.Run that implicitly handles the threads creation and management.

Async await is good for io bound tasks. Async await + Task.Run is good for cpu bound tasks.

In windows forms app we have the concept of synchronization context which ensures that only the single Ui thread runs all the code.

However if we use async await + Task.Run (which is good for cpu bound tasks), then the code inside Task.Run will run in separate thread.

Either ways code below the await will run on ui thread.

Where as in console app, whether we use async await or async await + Task.Run, code after the await will be run by multiple threads (from threadpool) in parallel due to the absence of synchronization context. An example of this is shown here: In console app, why does synchronous blocking code (Thread.Sleep(..)) when used in an awaited async task behave like multi threading?

In c#, threads are derived from the threadpool (I think that there is one thread per processor core, please correct me). However, unlike python, we don't have control to run code explicitly to target single or certain number of processors in c# do we?

variable
  • 8,262
  • 9
  • 95
  • 215
  • Is your question whether in C# there is a way to distinguish between threads running on a single cpu core vs threads running on different cpu cores? I think the answer is no but I'm not sure if I'm reading your question right. But you definitely can have more than one thread per cpu core. – Emperor Eto Jul 31 '21 at 19:20
  • Can I target my code to run on say 2 processors only? – variable Jul 31 '21 at 19:26
  • I don't believe you can do exactly that. However you CAN start an "apartment" thread even in a console app and thus it would behave like a WinForms or WPF app in that regard. But if you start a second apartment thread (which you also can do) I don't think you can control the CPU core it executes on. It *probably* will be a different one but not guaranteed. – Emperor Eto Jul 31 '21 at 19:37
  • *Either ways code below the await will run on ui thread* - not necessarily – Caius Jard Jul 31 '21 at 20:19
  • Caius- any example of why not necessarily? Unless you are talking about await within the code inside Task.Run? – variable Jul 31 '21 at 20:26
  • @variable if you're in a UI (single-threaded apartment / STA) thread, code below the `await` will always run on the same thread as above the `await`. That's sort of the point of STA and why it's used for WPF and WinForms. If you're in a worker thread, code below the `await` might not run on the same thread. So I think STA thread - but in a Console app - is what you're looking for? Or at least it's the closest I can think of to what you're describing Python allows you to do. – Emperor Eto Jul 31 '21 at 20:40
  • Are you talking about [processor affinity](https://stackoverflow.com/questions/2510593/how-can-i-set-processor-affinity-in-net), where you choose on which exact CPU processor(s) your program will run, or about imposing a [limit to the number of CPU processors](https://stackoverflow.com/questions/11432084/limit-number-of-processors-used-in-threadpool) that are allowed to run threads of your program concurrently at any given moment? – Theodor Zoulias Aug 01 '21 at 04:10

2 Answers2

1

Take a look at ProcessorAffinity. But remind: choosing to run on a single CPU core doesn't solve thread synchronisation problems.

Steeeve
  • 824
  • 1
  • 6
  • 14
1

There are several things we need to address here:

Python multi-processing

Python multi-processing refers more to the concept of process rather than processors (as in CPU cores). The idea is that you can launch sections of code in a whole different process with it's own interpreter runtime (therefore it's own GIL), memory space, etc...

It does not mean that a single Python process can't use several cores at once!

It's a very common misconception regarding Python. Many Python operations do not hold the GIL. You can create your own examples by writing a function that does mostly heavy NumPy operations, and starting this function in 20 different threads at once. If you check the CPU usage you'll see that 20 cores are at ~100% usage. You can also use Numba to JIT functions in a way that doesn't hold the GIL.

Limiting Core Usage

The way to use 2 specific cores is via the OS by changing the processor affinity of the process. I'd want to hear more about why you'd want to limit core usage though, because it sounds like you're trying to do something that can be achieved much more cleanly. Maybe you want to run some background tasks but need a Semaphore in order to make sure that only 2 tasks at most run at once?

Threads vs Async/Await

The point of the whole async/await syntax is to spare you from micro-managing threads. The idea is that you as a programmer write what you want to do asynchronously, or in other words, what operations shouldn't block the execution of the calling method, and the JIT/Runtime will handle the execution for you. It can be on a single thread, multiple threads, etc...

There are cases like you mentioned with WPF where you need to keep in mind that certain things can only be done on the UI thread. However, we ideally want to occupy the UI thread as little as necessary, so we use Tasks for most of the work. We can also use the Dispatcher inside the background Tasks for specific UI thread code.

After the await

You mentioned that:

Either ways code below the await will run on ui thread.

The code after the "await" by default will attempt to resume on the previous context. In the UI thread it means that both sections (before and after the await) will execute on the UI thread. However, this can be avoided (and many times should be avoided) by using ConfigureAwait(false) call on the task that's being awaited.

asaf92
  • 1,557
  • 1
  • 19
  • 30
  • 1
    In addition to this awesome information, if you **do** feel like micro managing tasks and threads, since C# is bootstrapped you could also create your own implementation of the `TaskScheduler` and force all or some tasks to run on n threads. But this still doesn't solve the OS from managing core loads across several cores. – DekuDesu Aug 01 '21 at 11:54