Unable to understand why my program is using too much RAM

To give an overview of my project, I am writing a C# program that reads Parquet files, copies to a DataTable and then makes an SQL connection and using SqlBulkCopy dumps the data to an SQL server.

I am using paralling processing, but I have to mention that I am new to C# as well parallel computing. Most of the code I have built here was by using ChatGPT and Googling.

Now, my program is going to read a directory, gather all files with extension “.parquet” and store them in a string array.

string[] fileList = GetParquetFiles(activeDirectory[0]);

These files will be read in parallel and I am using SemaphoreSlim to limit the number of active parallel threads.

public static async Task ProcessParquetFileAsync(string[] fileList, string databaseName)
{
    int numberOfConcurrentFiles = 2;
    using (SemaphoreSlim semaphore = new SemaphoreSlim(numberOfConcurrentFiles))
    {
        List<Task> tasks = new List<Task>();
        foreach (var file in fileList)
        {
            await semaphore.WaitAsync();
            tasks.Add(Task.Run(async () =>
            {
                try
                {
                    await ReadAndDeployParquetFile(file, databaseName);
                }
                finally
                {
                    semaphore.Release();
                }
            }));
        }
        await Task.WhenAll(tasks);
    }
    
}

Let’s take a flow of 1 such thread.
Inside this thread, I am reading the whole Parquet file as a Table (I am using Parquet.NET library to read).

In each thread, I am reading the ParquetTable completely and copying the schema to a DataTable (Just the schema, no data).

Next, I am calculating batchSize to split and read the ParquetTable into “chunks”.
These chunks of data are again processed parallelly using SemaphoreSlim

public static async Task ReadAndDeployParquetFile(string filePath, string databasename)
{
    using (ParquetReader reader = await ParquetReader.CreateAsync(filePath, null))
    {
        string tableName = GetTableName(filePath);
        Table parquetTable = await reader.ReadAsTableAsync();
        DataTable dataTable = new DataTable();

        string sql = $"CREATE TABLE {tableName} (";
        foreach(Field field in parquetTable.Schema.Fields)
        {

            DataField? ptField = field as DataField;
            string columnName = ptField.Name;
            Type columnType = ptField.ClrType;
            dataTable.Columns.Add(columnName, columnType);
            sql += $"[{columnName}] {GetSqlDataType(columnType, field)},";
        }
        sql = sql.Trim(',') + ')';
        SQLConnection conn = new SQLConnection();
        conn.ExecuteSqlCommand(sql, tableName, databasename);

        int rowCount = parquetTable.Count;
        int batchSize = 10000;
        decimal parts = Decimal.Ceiling((decimal)rowCount / (decimal)batchSize);
        
        SemaphoreSlim semaphore = new SemaphoreSlim(Environment.ProcessorCount);
        List<Task> tasks = new List<Task>();
        Console.WriteLine($"File {tableName} has total batch {(int)parts}");
        for (int i= 0; i < (int)parts; i++)
        {
            await semaphore.WaitAsync();
            int currentPart = i;
            tasks.Add(Task.Run (() =>
            {
                try
                {
                    ProcessBatch(parquetTable, dataTable.Clone(), currentPart, batchSize, tableName, databasename);
                }
                finally
                {
                    semaphore.Release();
                }
            }));
        }
        await Task.WhenAll(tasks);
        
        
    }
}

Finally, it is added row-by-row into a new DataTable called partTable that each sub thread is given (The schema of main DataTable is cloned and sent across).

public static void ProcessBatch(Table parquetTable, DataTable partTable, int currentPart, int batchSize, string tableName, string databaseName)
{
    SQLConnection conn = new SQLConnection();
    int columnCount = parquetTable.Schema.Fields.Count;
    for (int i = currentPart * batchSize; (i < ((currentPart + 1) * batchSize)) && (i < parquetTable.Count); i++)
    {
        var row = parquetTable[i];
        var dataRow = partTable.NewRow();
        for (int j = 0; j < columnCount; j++)
        {
            if (row[j] != null)
            {
                dataRow[j] = row[j] ?? DBNull.Value;
            }
        }
        partTable.Rows.Add(dataRow);
    }
    conn.InsertTable(tableName, partTable, databaseName, currentPart);
    partTable.Dispose();
}

Now the issue is, there is a parquet file which as 2 million rows. The chunk size I have given is 100k, so now it would make 10 batches and run them in parallel but keep only 8 threads active at a time (Environment.ProcessorCount is 8 in my PC) and run the remaining 2 when any of the 8 frees up (correct me if I am wrong here).

The file itself is 24MB, but the RAM usage is shooting up to 3GB! How?
My understanding of how the program works is
When 1 sub-thread is done, it should free up all it’s memory. But it appears as if this is not happening.

I used dotMemory application to check the memory usage and the RAM consumption keeps going UP and never comes down at any point.

Can anyone help me understand why the memory is not clearing up after the sub-thread job is done and also help me fix the code to reduce RAM usage?
Again, I am very new to C# and even more new to parallel computing, so please go easy on explanation.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật