Updated on 22 Sep 2025 by Admin

Bridging the Gap – Advanced Python.NET: Performance, the GIL, and Deployment

While building applications that integrate multiple technologies, you must consider how they operate together at a lower level. This is especially true for Python, where the Global Interpreter Lock (GIL) plays a crucial role.

In this article, we will demystify the Global Interpreter Lock (GIL) in Python and explore how it affects multi-threading. We will also discuss some packaging practices for your integrated applications so they can be implemented on other machines without error.


Setting Up the Project

Let's create a new .NET console application to understand GIL and multi-threading in Python.NET.

  1. You can do so using an IDE or directly from the command prompt by running the following command:

dotnet new console -o PythonNetGIL

  1. Go to the project directory:

cd PythonNetGIL

  1. Install Python.NET package:

dotnet add package pythonnet


Understanding the Global Interpreter Lock (GIL)

The Global Interpreter Lock (GIL) is a mutex (a lock) that ensures that only one native thread runs at a time. Although Python is a multi-threaded language, GIL prevents multiple Python threads from running simultaneously. This sometimes becomes a major bottleneck in CPU-intensive applications.

To see how GIL works, we will write a Python script that performs a CPU-intensive yet straightforward task on both single and multi-threads. In our C# application, we will time both executions to see GIL in action.


Configuring the C# Project to Copy Files

Here, we will set up our C# program to copy the Python script to the project's output folder when we build it. This is an important step to prevent "FileNotFound" errors.

  1. Launch PythonNetGIL.csproj file in a code editor and add the following <ItemGroup> inside the main <Project> label:

<ItemGroup>
  <Content Include="multi_thread_demo.py">
    <CopyToOutputDirectory>Always</CopyToOutputDirectory>
  </Content>
</ItemGroup>

  1. Save the file.

Creating the Python Module

Now, it's time to create our Python module.

  1. Inside your project directory, create a new Python file named multi_thread_demo.py.
  2. Add the following code to the file:

# multi_thread_demo.py
import threading
import time


def cpu_bound_task(n):
    """A simple CPU-bound task."""
    result = 0
    for i in range(n):
        result += i * i
    return result


def single_threaded_demo():
    """Demonstrates a single-threaded CPU-bound task."""
    print("Python: Starting single-threaded demo...")
    start_time = time.time()
    cpu_bound_task(100_000_000)
    end_time = time.time()
    duration = end_time - start_time
    print(f"Python: Single-threaded duration: {duration:.2f} seconds")
    return duration


def multi_threaded_demo():
    """Attempts to perform a CPU-bound task with multiple threads."""
    print("Python: Starting multi-threaded demo...")
    start_time = time.time()

    # We'll use a single list to store our threads.
    threads = []

    # Create two threads to perform the same task.
    for _ in range(2):
        thread = threading.Thread(target=cpu_bound_task, args=(100_000_000,))
        threads.append(thread)
        thread.start()

    # Wait for both threads to complete.
    for thread in threads:
        thread.join()

    end_time = time.time()
    duration = end_time - start_time
    print(f"Python: Multi-threaded duration: {duration:.2f} seconds")
    return duration


Writing the C# Integration Code

Finally, we need to edit our Program.cs file and integrate the Python module into it to see GIL in action.

  1. Open Program.cs in a code editor and add the following code to it:

// Program.cs

using System;
using System.IO;
using Python.Runtime;

class Program
{
    static void Main(string[] args)
    {
        // Set the Python DLL. This is crucial for Python.NET to find the correct
        // Python installation. Replace the path with the location of your pythonXX.dll.
        Runtime.PythonDLL = "YOUR_PYTHON_INSTALLATION_PATH";

        // Initialize the Python engine.
        PythonEngine.Initialize();

        try
        {
            using (Py.GIL())
            {
                // Set the path to our Python module.
                string appDirectory = AppDomain.CurrentDomain.BaseDirectory;
                string pythonModulePath = Path.Combine(appDirectory, "multi_thread_demo.py");

                // Get a reference to Python's sys module and add our module's directory to the search path.
                dynamic sys = Py.Import("sys");

                // Use a robust method to call the `insert` function on the Python list.
                PyObject pyPath = sys.path;
                pyPath.GetAttr("insert").Invoke(new PyInt(0), new PyString(Path.GetDirectoryName(pythonModulePath)));

                // Import the Python module.
                dynamic demo = Py.Import("multi_thread_demo");

                // Call the single-threaded function.
                double singleThreadedDuration = demo.single_threaded_demo();

                Console.WriteLine("\n-------------------------------------------");

                // Call the multi-threaded function.
                double multiThreadedDuration = demo.multi_threaded_demo();

                Console.WriteLine("\n--- C# Results ---");
                Console.WriteLine($"Single-threaded Python took {singleThreadedDuration:F2} seconds.");
                Console.WriteLine($"Multi-threaded Python took {multiThreadedDuration:F2} seconds.");
            }
        }
        finally
        {
            // Add a try-catch block to handle the shutdown exception in modern .NET.
            try
            {
                PythonEngine.Shutdown();
            }
            catch (System.Exception ex)
            {
                Console.WriteLine($"An exception occurred during PythonEngine.Shutdown(): {ex.Message}");
                // This exception is harmless and can be ignored to allow the application to exit gracefully.
            }
        }
    }
}

Note: Before running the above code, ensure to replace "YOUR_PYTHON_INSTALLATION_PATH" with the actual path of your Python interpreter. It should look something like:

@"C:\\Users\\raoha\\AppData\\Local\\Programs\\Python\\Python312\\python312.dll"


Running the Application

The last step is to run the application. To do so, execute the following command in CMD:


dotnet run

If everything is set up correctly, you will see an output something to the following:


Python: Starting single-threaded demo...
Python: Single-threaded duration: 15.38 seconds
-------------------------------------------
Python: Starting multi-threaded demo...
Python: Multi-threaded duration: 28.88 seconds
--- C# Results ---
Single-threaded Python took 15.38 seconds.
Multi-threaded Python took 28.88 seconds.

Please note that your output may differ based on your CPU speed. In the above output, you can see that GIL does not allow Python to leverage multiple CPU cores for CPU-bound tasks.


Deployment Strategies

When your application is ready for deployment and you want to deploy it to other users, you have the following primary options:

  1. Require a Pre-Installed Python Environment: In this approach, we assume that the user has a compatible Python version and other dependencies like Pandas and scikit-learn installed on their machine.
  2. Bundle the Python Interpreter: This approach gives a more seamless user experience. It allows you to package the Python interpreter and all its dependencies in your application's executable. This makes your application self-contained and eliminates the need for the user to install Python and other libraries explicitly. To package your application, you can use pyinstaller. It allows you to create a single executable from your Python script, which then communicates with a C# application via standard input/output streams as a separate process.

For the beginner-level applications, requiring the pre-installed environment is sufficient. However, for professional applications, we recommend using pyinstaller to package the application with all its dependencies.

Congratulations! You have come really far in exploring the power of Python.NET in bridging the gap between Python and C#. You can now build and integrate your AI models with .NET applications seamlessly with the tools you have learned in this series.


You need to login to post a comment.

Sharpen Your Skills with These Next Guides