C
C#

C to C#

10 lessons

Progress0%
1Variables & Types2Functions & Methods3Arrays & Lists4Structs → Classes5Memory Management6Strings7File I/O8Object-Oriented Programming9Generics and Collections10Async and Concurrency
All Mirror Courses
C
C#
Async and Concurrency
MirrorLesson 10 of 10
Lesson 10

Async and Concurrency

C# async/await, Task, Thread vs C pthreads — modern concurrency patterns

Introduction

In this lesson, you'll learn about async and concurrency in C#. Coming from C, you already have a foundation for understanding this concept. We'll build on that knowledge while highlighting the key differences.

Mirror Card
C
From C:

In C, you're familiar with c# async/await, task, thread vs c pthreads — modern concurrency patterns.

C#
In C#:

C# has its own approach to c# async/await, task, thread vs c pthreads — modern concurrency patterns, which we'll explore step by step.

The C# Way

Let's see how C# handles this concept. Here's a typical example:

C#
C# Example
using System;
using System.Threading;
using System.Threading.Tasks;

// async/await — non-blocking I/O (no threads blocked)
public async Task<string> FetchAsync(string url)
{
    using var client = new HttpClient();
    return await client.GetStringAsync(url);  // non-blocking
}

// Run multiple async tasks in parallel
public async Task RunParallel()
{
    var t1 = FetchAsync("https://api1.example.com");
    var t2 = FetchAsync("https://api2.example.com");
    string[] results = await Task.WhenAll(t1, t2);
    Console.WriteLine(results[0]);
}

// Thread-safe counter
private int _counter = 0;
Interlocked.Increment(ref _counter);   // atomic, no lock needed

// Mutex/lock for complex critical sections
private readonly object _lock = new();
lock (_lock)
{
    _counter++;
    // thread-safe block
}

// Parallel.ForEach for CPU-bound work
Parallel.ForEach(items, item => {
    Process(item);  // runs on thread pool
});

// Task.Run for CPU-bound work off the UI thread
var result = await Task.Run(() => ExpensiveComputation());

Comparing to C

Here's how you might have written similar code in C:

C
C (What you know)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// Shared data + mutex
int counter = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void *increment(void *arg) {
    for (int i = 0; i < 1000; i++) {
        pthread_mutex_lock(&lock);
        counter++;
        pthread_mutex_unlock(&lock);
    }
    return NULL;
}

// Parallel workers
int main(void) {
    pthread_t t1, t2;
    pthread_create(&t1, NULL, increment, NULL);
    pthread_create(&t2, NULL, increment, NULL);
    pthread_join(t1, NULL);   // wait for t1
    pthread_join(t2, NULL);   // wait for t2
    printf("counter=%d\n", counter);  // 2000

    pthread_mutex_destroy(&lock);
    return 0;
}
Mirror Card
C
From C:

You may be used to different syntax or behavior.

C#
In C#:

async/await enables non-blocking I/O without threads; C needs pthreads for any concurrency

Mirror Card
C
From C:

You may be used to different syntax or behavior.

C#
In C#:

Task<T> is a promise/future; await suspends the method without blocking a thread

Mirror Card
C
From C:

You may be used to different syntax or behavior.

C#
In C#:

Interlocked.Increment is atomic; C needs pthread_mutex for the same

Mirror Card
C
From C:

You may be used to different syntax or behavior.

C#
In C#:

lock() is simpler than pthread_mutex_lock/unlock — automatic unlock even on exception

Mirror Card
C
From C:

You may be used to different syntax or behavior.

C#
In C#:

Task.WhenAll runs tasks in parallel and waits for all — no pthread_join loop

Step-by-Step Breakdown

1. async/await

async/await transforms I/O-bound code into non-blocking operations. No thread is blocked while waiting — unlike C's blocking read/recv.

C
C
// C: blocking recv() — thread stuck waiting
recv(sock, buf, len, 0);
process(buf);
C#
C#
// C#: async — thread freed while awaiting I/O
public async Task<string> ReadAsync()
{
    string data = await File.ReadAllTextAsync("f.txt");
    return Process(data);
}

2. Task.WhenAll

Task.WhenAll runs multiple tasks in parallel and waits for all. Much cleaner than creating pthreads and joining them.

C
C
pthread_t t1, t2;
pthread_create(&t1, NULL, fn1, NULL);
pthread_create(&t2, NULL, fn2, NULL);
pthread_join(t1, NULL); pthread_join(t2, NULL);
C#
C#
var task1 = DoWork1Async();
var task2 = DoWork2Async();
await Task.WhenAll(task1, task2);
// both complete before continuing

3. Thread Safety

lock {} auto-unlocks when the block exits — even on exception. Interlocked provides lock-free atomic operations for simple counters.

C
C
pthread_mutex_lock(&lock);
counter++;
pthread_mutex_unlock(&lock);
// What if exception? Lock stays locked!
C#
C#
// Atomic counter — no lock needed
Interlocked.Increment(ref _counter);

// Complex section — auto-unlock guaranteed
lock (_lock) {
    _data.Add(item);
    _count++;
}

4. Parallel.ForEach

Parallel.ForEach distributes CPU-bound work across thread pool threads. Much simpler than manual pthread management.

C
C
// C: create N threads, split work, join all
pthread_t threads[N];
for(int i=0;i<N;i++) pthread_create(&threads[i], NULL, fn, &chunks[i]);
for(int i=0;i<N;i++) pthread_join(threads[i], NULL);
C#
C#
Parallel.ForEach(items, item => Process(item));
// Or with options:
Parallel.ForEach(items,
    new ParallelOptions { MaxDegreeOfParallelism = 4 },
    item => Process(item));

Common Mistakes

When coming from C, developers often make these mistakes:

  • async/await enables non-blocking I/O without threads; C needs pthreads for any concurrency
  • Task<T> is a promise/future; await suspends the method without blocking a thread
  • Interlocked.Increment is atomic; C needs pthread_mutex for the same
Common Pitfall
Don't assume C# works exactly like C. While the concepts may be similar, the syntax and behavior can differ significantly.

Key Takeaways

  • async/await for I/O-bound work — no thread blocked while waiting
  • Task.WhenAll for parallel fan-out — replaces pthread_create/join loop
  • lock {} auto-unlocks (even on exception); Interlocked for atomic operations
  • Parallel.ForEach for CPU-bound parallel work — no manual thread management
Rule of Thumb
The best way to learn is by doing. Try rewriting some of your C code in C# to practice these concepts.
PreviousFinish