Part 2 - Performance & Concurrency Essentials in C#: Memory, Async, and High-Performance Primitives

Published: (January 7, 2026 at 06:03 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

Excerpt
This post covers the C# performance essentials for enterprise backends: memory semantics (stack vs heap, value vs reference, GC), async/await with cancellation, streaming via IAsyncEnumerable, concurrency primitives, Span, pooling, and pitfalls with boxing and large structs.

Memory model: Stack vs Heap, Value vs Reference, GC

Diagram: Memory at a glance

+---------------------------+           +--------------------------------+
|           Stack           |           |              Heap              |
|---------------------------|           |--------------------------------|
| int x = 42                |           | new User() { Name = "Alice" } |
| Point p (struct inline)   |   --->    |  reference held on the stack   |
| local frames, by-value    |           | objects, arrays, strings       |
+---------------------------+           +--------------------------------+

Essentials

  • Value types (struct, record struct) are copied by value and often stored inline; cheap for tiny models.
  • Reference types (class, record) live on the heap; managed by the GC.

Best practices

  • Reduce boxing/unboxing (e.g., avoid storing struct in object; prefer generic collections).
  • Use StringBuilder for heavy concatenation; prefer ArrayPool and Span/Memory for parsing and allocations.

Boxing pitfall

struct Counter { public int Value; }
object o = new Counter { Value = 5 }; // boxing (allocates)
Counter c = (Counter)o;               // unboxing (copy)

ArrayPool & Span for parsing

using System.Buffers;

var pool = ArrayPool.Shared;
byte[] buffer = pool.Rent(4096);
try
{
    // Fill buffer, then process without extra allocations
    var span = new Span(buffer, 0, 4096);
    // parse span...
}
finally
{
    pool.Return(buffer);
}

GC awareness

  • Allocation spikes can trigger GC; keep hot paths allocation‑light.
  • Prefer struct enumerators and readonly struct for tight loops when practical.

Async/await & cancellation, IAsyncEnumerable, concurrency primitives

Diagram: Async request flow

sequenceDiagram
  participant Client
  participant Controller
  participant Service
  participant ExternalAPI

  Client->>Controller: HTTP GET /items
  Controller->>Service: GetItemsAsync(ct)
  Service->>ExternalAPI: GET /items (ct)
  ExternalAPI-->>Service: 200 OK (streamed)
  Service-->>Controller: IAsyncEnumerable
  Controller-->>Client: Chunked JSON response

Guidelines

  • async/await is for I/O‑bound work; for CPU‑bound tasks use Task.Run carefully.
  • Always pass a CancellationToken; prefer timeouts and propagate cancellation across layers.
  • For streaming, use IAsyncEnumerable; avoid buffering large sets in memory.

Cancellation & timeout

public async Task FetchAsync(HttpClient http, string url, CancellationToken ct)
{
    using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
    cts.CancelAfter(TimeSpan.FromSeconds(5));
    return await http.GetStringAsync(url, cts.Token).ConfigureAwait(false);
}

Streaming results

public async IAsyncEnumerable GetNumbersAsync([EnumeratorCancellation] CancellationToken ct = default)
{
    for (int i = 0; i ();
}
_ = Task.Run(async () =>
{
    await foreach (var item in channel.Reader.ReadAllAsync(ct))
    {
        // process item
    }
});
await channel.Writer.WriteAsync("work-item", ct);
channel.Writer.Complete();

Library code tip

  • In libraries, prefer ConfigureAwait(false) to avoid deadlocks in legacy sync contexts.

Span, pooling, boxing, large struct caveats

Span notes

Span is a stack‑only ref struct for high‑performance slicing—no heap allocations.

  • Cannot cross await or be captured in lambdas; use Memory/ReadOnlyMemory for async boundaries.
ReadOnlySpan<char> span = "1234".AsSpan();
int total = 0;
foreach (var ch in span) total += (ch - '0');

Large struct caveats

Large structs incur copy cost when passed/returned; prefer classes or use in parameters for read‑only refs.

public readonly struct BigValue
{
    public readonly int A, B, C, D, E, F, G, H;
}

int Sum(in BigValue v) => v.A + v.B + v.C + v.D + v.E + v.F + v.G + v.H;

Pooling

  • Prefer ArrayPool or object pools in hot paths to reduce GC pressure.

CTA

Next up: Part 3 — Production‑Ready Practices (resource management, testing/diagnostics, logging/telemetry, security & reliability). Share your performance tips in the comments!

Series Navigation

Back to Blog

Related posts

Read more »

MiniScript Road Map for 2026

2026 Outlook With 2025 coming to a close, it’s time to look ahead to 2026! MiniScript is now eight years old. Many programming languages really come into their...

An Honest Review of Go (2025)

Article URL: https://benraz.dev/blog/golang_review.html Comments URL: https://news.ycombinator.com/item?id=46542253 Points: 58 Comments: 50...