Here is my example of reading records from Database using Tasks in background, and Threads.

As Jon Skeet wrote on Stack Overflow:

A task returned by Task.Run() really is saying "I want you to execute this code separately"; the exact thread on which that code executes depends on a number of factors.
So, here is first my method with async. First install Microsoft.Data.SqlClient here notice that old System.Data.SqlClient is deprecated.

Now the method:

return Task.Run(() => ThreadReadFromDatabaseAsync(connectionString, sql));

private async Task ThreadReadFromDatabaseAsync(string connectionString, string sql)
{
	DateTime start = DateTime.Now;
	try
	{
		await using SqlConnection connection = new SqlConnection(connectionString);
		await connection.OpenAsync();
		await using SqlCommand command = new SqlCommand(sql, connection);
		command.CommandTimeout = 3600;
		await using SqlDataReader reader = await command.ExecuteReaderAsync();
		int recdCnt = 0;
		while (reader.Read())
		{
			UpdateUi.Execute($"{start} Reading using tasks record {recdCnt++}", LblRecordCount, Form);

			object[] values = new object[reader.FieldCount];
			reader.GetValues(values);
		}
	}
	catch (Exception ex)
	{
		UpdateUi.Execute($"Error in task execution: {ex.Message}", LblError, Form);
	}
	finally
	{
		UpdateUi.Execute($"{DateTime.Now} Done with task execution", LblStatus, Form);
	}
}
Here notice line:
while (reader.Read())
That I am not using ReadAsync. The problem is that at ReadAsync will stuck on reading from DB after few hundert records, this looks like bug described here.

Now another example with thread:

var thread = new Thread(() => ThreadReadFromDatabase(connectionString, sql))
{
	Name = "ThreadReadFromDatabase",
	IsBackground = true
};
thread.Start();

private void ThreadReadFromDatabase(string connectionString, string sql)
{
	DateTime start = DateTime.Now;

	try
	{
		using SqlConnection connection = new SqlConnection(connectionString);
		connection.Open();
		using SqlCommand command = new SqlCommand(sql, connection);
		command.CommandTimeout = 3600;
		using SqlDataReader reader = command.ExecuteReader();
		int recdCnt = 0;
		while (reader.Read())
		{
			UpdateUi.Execute($"{start} Reading using threads record {recdCnt++}", LblRecordCount, Form);

			object[] values = new object[reader.FieldCount];
			reader.GetValues(values);
		}
	}
	catch (Exception ex)
	{
		UpdateUi.Execute($"Error in thread execution: {ex.Message}", LblError, Form);
	}
	finally
	{
		UpdateUi.Execute($"{DateTime.Now} Done with thread execution", LblStatus, Form);
	}
}
Full example download from here

UPDATE 2025-03-12: Avoid using Task.Run for long-running work that blocks the thread

Also from the book Concurrency in C# Cookbook by Stephen Cleary:

As soon as you type new Thread(), it’s over; your project already has legacy code

Also check this Stack Overflow answer.