Concurrency and Asynchronous Programming in C#

Introduction

Concurrency and asynchronous programming are essential techniques for building responsive and scalable applications. Concurrency allows multiple tasks to run seemingly simultaneously, while asynchronous programming enables a program to start a potentially long-running task and continue executing other tasks without waiting for the first task to complete. In C#, these concepts are primarily implemented using threads, tasks, and the async/await keywords.

Interview Question: Task.Run vs. Task.Factory.StartNew

Explain the difference between Task.Run and Task.Factory.StartNew.

Code Example

using System;
using System.Threading.Tasks;

public class ConcurrencyExample
{
    public static void Main(string[] args)
    {
        // Using Task.Run
        Task taskRun = Task.Run(() =>
        {
            Console.WriteLine("Task.Run: Executing on thread {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
        });

        // Using Task.Factory.StartNew
        Task taskFactory = Task.Factory.StartNew(() =>
        {
            Console.WriteLine("Task.Factory.StartNew: Executing on thread {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
        });

        Task.WaitAll(taskRun, taskFactory);

        Console.WriteLine("Main thread completed.");
        Console.ReadKey();
    }
}

Detailed Explanation

Task.Run

Task.Factory.StartNew

Differences, Use Cases, and Potential Pitfalls

In most cases, Task.Run is sufficient. Use Task.Factory.StartNew when you need more control over task creation and scheduling.

Code Debugging Task: Race Condition

Find and fix the race condition in the following code.

using System;
using System.Threading;

public class RaceConditionExample
{
    static int counter = 0;

    public static void Main(string[] args)
    {
        Thread t1 = new Thread(IncrementCounter);
        Thread t2 = new Thread(IncrementCounter);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine("Counter value: {0}", counter);
        Console.ReadKey();
    }

    static void IncrementCounter()
    {
        for (int i = 0; i < 100000; i++)
        {
            counter++; // Race condition
        }
    }
}

Explanation and Corrected Code

Race Condition

The race condition occurs because multiple threads are trying to access and modify the counter variable simultaneously without any synchronization. This can lead to lost updates, where one thread overwrites the changes made by another thread.

Corrected Code

using System;
using System.Threading;

public class RaceConditionExample
{
    static int counter = 0;
    static object lockObject = new object();

    public static void Main(string[] args)
    {
        Thread t1 = new Thread(IncrementCounter);
        Thread t2 = new Thread(IncrementCounter);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine("Counter value: {0}", counter);
        Console.ReadKey();
    }

    static void IncrementCounter()
    {
        for (int i = 0; i < 100000; i++)
        {
            lock (lockObject)
            {
                counter++; // Race condition fixed with lock
            }
        }
    }
}

Explanation of the Fix

Дополнительные вопросы и ответы (Additional Questions and Answers)

Question: Is the List type a thread-safe collection?

The List type can be thread-safe for read operations. User code must provide all synchronization when adding/removing elements concurrently across multiple threads.

Question: Thread vs Task - usage examples?

The Thread class creates and controls a thread. It takes a method to execute in the thread. The Task class allows running a separate long-running task. It runs asynchronously in a thread pool thread but can also run synchronously.

var t = new Thread(() => Thread.Sleep(1000));
t.IsBackground = false; // Foreground thread - system waits for its completion
t.Start();
Task.Run(() => Task.Delay(1000)).Wait(); // Using TPL
Question: Which keyword is used to block concurrent execution of code sections by multiple threads?

The lock keyword is used to prevent concurrent execution of code sections by multiple threads. lock defines a block where the code becomes inaccessible to other threads until the current thread completes its execution.