CommunityCodierung & Entwicklunggithub.com

csharp-concurrency-patterns

Auswahl der richtigen Parallelitätsabstraktion in .NET – von async/await für I/O bis zu Channels für Producer/Consumer und Akka.NET für zustandsbehaftete Entitätenverwaltung. Vermeiden Sie Locks und manuelle Synchronisation, wo immer möglich.

Was ist csharp-concurrency-patterns?

Dieses Skill dient als Leitfaden für .NET-Entwickler, um die angemessene Parallelitätsabstraktion für eine gegebene Aufgabe auszuwählen. Es beginnt mit der grundlegenden Empfehlung, `async/await` für die meisten I/O-gebundenen Operationen zu verwenden und nur bei spezifischen Anforderungen wie CPU-gebundener Parallelarbeit (`Parallel.ForEachAsync`), Producer/Consumer-Szenarien (`System.Threading.Channels`), UI-Ereigniskomposition (Reactive Extensions) oder zustandsbehafteter Entitätenverwaltung (Akka.NET Actors) zu komplexeren Werkzeugen zu greifen. Das Skill betont die Philosophie, Shared Mutable State zu vermeiden und Locks nur als letzten Ausweg einzusetzen. Es enthält einen Entscheidungsbaum, Beispielcode für jeden Abstraktionsgrad und eine Übersicht über Anti-Patterns wie Blockieren in async Code oder direktes Thread-Management. Dieses Skill ist plattformunabhängig für jeden .NET-Entwickler geeignet, der saubere, wartbare und skalierbare nebenläufige Systeme schreiben möchte.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Aaronontheweb/dotnet-skills/tree/main/skills/csharp-concurrency-patterns

Installed? Explore more Codierung & Entwicklung skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

.NET Parallelität: Das richtige Werkzeug wählen

Wann dieses Skill verwenden

Verwenden Sie dieses Skill, wenn:

  • Sie entscheiden müssen, wie Sie nebenläufige Operationen in .NET handhaben
  • Sie abwägen, ob Sie async/await, Channels, Akka.NET oder andere Abstraktionen einsetzen
  • Sie versucht sind, Locks, Semaphore oder andere Synchronisationsprimitive zu verwenden
  • Sie Datenströme mit Backpressure, Batching oder Debouncing verarbeiten müssen
  • Sie Zustände über mehrere nebenläufige Entitäten hinweg verwalten

Referenzdateien

  • advanced-concurrency.md: Akka.NET Streams, Reactive Extensions, Akka.NET Actors (Entity-per-Actor, Zustandsautomaten, Cluster Sharding) und asynchrone lokale Funktionsmuster

Die Philosophie

Fangen Sie einfach an und eskalieren Sie nur bei Bedarf.

Die meisten Parallelitätsprobleme können mit async/await gelöst werden. Greifen Sie nur dann zu ausgefeilteren Werkzeugen, wenn Sie einen spezifischen Bedarf haben, den async/await nicht sauber adressieren kann.

Vermeiden Sie nach Möglichkeit gemeinsamen veränderlichen Zustand. Der beste Weg, mit Parallelität umzugehen, ist, sie wegzudesignen. Unveränderliche Daten, Nachrichtenaustausch und isolierter Zustand (wie bei Aktoren) eliminieren ganze Kategorien von Fehlern.

Locks sollten die Ausnahme sein, nicht die Regel. Wenn Sie gemeinsamen veränderlichen Zustand nicht vermeiden können:

  1. Erste Wahl: Umgestalten, um ihn zu vermeiden (Unveränderlichkeit, Nachrichtenaustausch, Aktor-Isolation)
  2. Zweite Wahl: System.Collections.Concurrent verwenden (ConcurrentDictionary usw.)
  3. Dritte Wahl: Channel<T> verwenden, um den Zugriff durch Nachrichtenaustausch zu serialisieren
  4. Letzter Ausweg: lock für einfache, kurzlebige kritische Abschnitte verwenden

Entscheidungsbaum

Was versuchen Sie zu tun?
│
├─► Auf I/O warten (HTTP, Datenbank, Datei)?
│   └─► async/await verwenden
│
├─► Eine Sammlung parallel verarbeiten (CPU-gebunden)?
│   └─► Parallel.ForEachAsync verwenden
│
├─► Producer/Consumer-Muster (Arbeitswarteschlange)?
│   └─► System.Threading.Channels verwenden
│
├─► UI-Ereignisbehandlung (Debounce, Throttle, Kombinieren)?
│   └─► Reactive Extensions (Rx) verwenden
│
├─► Serverseitige Stream-Verarbeitung (Backpressure, Batching)?
│   └─► Akka.NET Streams verwenden
│
├─► Zustandsautomaten mit komplexen Übergängen?
│   └─► Akka.NET Actors (Become-Muster) verwenden
│
├─► Zustand für viele unabhängige Entitäten verwalten?
│   └─► Akka.NET Actors (Entity-per-Actor) verwenden
│
├─► Mehrere asynchrone Operationen koordinieren?
│   └─► Task.WhenAll / Task.WhenAny verwenden
│
└─► Nichts davon passt?
    └─► Fragen Sie sich: „Brauche ich wirklich gemeinsamen veränderlichen Zustand?“
        ├─► Ja → Umgestalten in Betracht ziehen, um ihn zu vermeiden
        └─► Wirklich unvermeidbar → Channels oder Actors verwenden, um den Zugriff zu serialisieren

Stufe 1: async/await (Standardwahl)

Verwenden für: I/O-gebundene Operationen, nicht blockierende Wartevorgänge, die meisten alltäglichen Parallelitätsanforderungen.

// Einfaches asynchrones I/O
public async Task<Order> GetOrderAsync(string orderId, CancellationToken ct)
{
    var order = await _database.GetAsync(orderId, ct);
    var customer = await _customerService.GetAsync(order.CustomerId, ct);
    return order with { Customer = customer };
}

// Parallele asynchrone Operationen (wenn unabhängig)
public async Task<Dashboard> LoadDashboardAsync(string userId, CancellationToken ct)
{
    var ordersTask = _orderService.GetRecentOrdersAsync(userId, ct);
    var notificationsTask = _notificationService.GetUnreadAsync(userId, ct);
    var statsTask = _statsService.GetUserStatsAsync(userId, ct);

    await Task.WhenAll(ordersTask, notificationsTask, statsTask);

    return new Dashboard(
        Orders: await ordersTask,
        Notifications: await notificationsTask,
        Stats: await statsTask);
}

Wichtige Prinzipien: Immer CancellationToken akzeptieren. ConfigureAwait(false) in Bibliothekscode verwenden. Nicht auf asynchronem Code blockieren.


Stufe 2: Parallel.ForEachAsync (CPU-gebundene Parallelität)

Verwenden für: Parallele Verarbeitung von Sammlungen, wenn die Arbeit CPU-gebunden ist oder Sie kontrollierte Parallelität benötigen.

public async Task ProcessOrdersAsync(
    IEnumerable<Order> orders,
    CancellationToken ct)
{
    await Parallel.ForEachAsync(
        orders,
        new ParallelOptions
        {
            MaxDegreeOfParallelism = Environment.ProcessorCount,
            CancellationToken = ct
        },
        async (order, token) =>
        {
            await ProcessOrderAsync(order, token);
        });
}

Wann NICHT verwenden: Reine I/O-Operationen, wenn die Reihenfolge wichtig ist, wenn Sie Backpressure benötigen.


Stufe 3: System.Threading.Channels (Producer/Consumer)

Verwenden für: Arbeitswarteschlangen, Producer/Consumer-Muster, Entkopplung von Produzenten und Konsumenten.

public class OrderProcessor
{
    private readonly Channel<Order> _channel;

    public OrderProcessor()
    {
        _channel = Channel.CreateBounded<Order>(new BoundedChannelOptions(100)
        {
            FullMode = BoundedChannelFullMode.Wait
        });
    }

    // Producer
    public async Task EnqueueOrderAsync(Order order, CancellationToken ct)
    {
        await _channel.Writer.WriteAsync(order, ct);
    }

    // Consumer (als Hintergrundaufgabe ausführen)
    public async Task ProcessOrdersAsync(CancellationToken ct)
    {
        await foreach (var order in _channel.Reader.ReadAllAsync(ct))
        {
            await ProcessOrderAsync(order, ct);
        }
    }

    public void Complete() => _channel.Writer.Complete();
}

Channels sind gut für: Entkopplung von Geschwindigkeit, Puffern mit Backpressure, Fan-Out an Arbeiter, Hintergrundwarteschlangen.

Channels sind NICHT gut für: Komplexe Stream-Operationen (Batching, Windowing), zustandsbehaftete Verarbeitung pro Entität, ausgefeilte Überwachung.


Stufe 4+: Akka.NET Streams, Reactive Extensions, Actors

Für fortgeschrittene Szenarien, die Stream-Verarbeitung, UI-Ereigniskomposition oder zustandsbehaftete Entitätenverwaltung erfordern, siehe advanced-concurrency.md.

Akka.NET Streams zeichnen sich durch serverseitiges Batching, Drosselung und Backpressure aus. Reactive Extensions sind ideal für die UI-Ereigniskomposition. Akka.NET Actors bewältigen Entity-per-Actor-Muster, Zustandsautomaten mit Become() und verteilte Systeme über Cluster Sharding.


Anti-Patterns: Was vermieden werden sollte

Locks für Geschäftslogik

// SCHLECHT: Locks zum Schützen von gemeinsamem Zustand verwenden
private readonly object _lock = new();
private Dictionary<string, Order> _orders = new();

public void UpdateOrder(string id, Action<Order> update)
{
    lock (_lock) { if (_orders.TryGetValue(id, out var order)) update(order); }
}

// GUT: Einen Actor oder Channel verwenden, um den Zugriff zu serialisieren

Manuelles Thread-Management

// SCHLECHT: Threads manuell erstellen
var thread = new Thread(() => ProcessOrders());
thread.Start();

// GUT: Task.Run oder bessere Abstraktionen verwenden
_ = Task.Run(() => ProcessOrdersAsync(cancellationToken));

Blockieren in asynchronem Code

// SCHLECHT: Auf async blockieren - Deadlock-Risiko!
var result = GetDataAsync().Result;

// GUT: Async durchgängig
var result = await GetDataAsync();

Gemeinsamer veränderlicher Zustand ohne Schutz

// SCHLECHT: Mehrere Aufgaben mutieren gemeinsamen Zustand
var results = new List<Result>();
await Parallel.ForEachAsync(items, async (item, ct) =>
{
    var result = await ProcessAsync(item, ct);
    results.Add(result); // Wettlaufsituation!
});

// GUT: ConcurrentBag verwenden
var results = new ConcurrentBag<Result>();

Kurzreferenz: Welches Werkzeug wann?

BedarfWerkzeugBeispiel
Auf I/O wartenasync/awaitHTTP-Aufrufe, Datenbankabfragen
Parallele CPU-ArbeitParallel.ForEachAsyncBildverarbeitung, Berechnungen
ArbeitswarteschlangeChannel<T>Hintergrund-Job-Verarbeitung
UI-Ereignisse mit Debounce/ThrottleReactive ExtensionsSuchen-während-der-Eingabe, Auto-Speichern
Serverseitiges Batching/DrosselungAkka.NET StreamsEreignisaggregation, Ratenbegrenzung
ZustandsautomatenAkka.NET ActorsZahlungsabläufe, Auftragslebenszyklen
EntitätszustandsverwaltungAkka.NET ActorsAuftragsverwaltung, Benutzersitzungen
Mehrere asynchrone Vorgänge auslösenTask.WhenAllDashboard-Daten laden
Rennen mehrerer asynchroner VorgängeTask.WhenAnyTimeout mit Fallback
Periodische ArbeitPeriodicTimerHealth Checks, Polling

Der Eskalationspfad

async/await (hier beginnen)
    │
    ├─► Parallelität nötig? → Parallel.ForEachAsync
    │
    ├─► Producer/Consumer nötig? → Channel<T>
    │
    ├─► UI-Ereigniskomposition nötig? → Reactive Extensions
    │
    ├─► Serverseitige Stream-Verarbeitung nötig? → Akka.NET Streams
    │
    └─► Zustandsautomaten oder Entitätsverwaltung nötig? → Akka.NET Actors

Eskalieren Sie nur, wenn Sie einen konkreten Bedarf haben. Greifen Sie nicht „nur für den Fall“ zu Actors oder Streams.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

akka-hosting-actor-patterns

Patterns for building entity actors with Akka.Hosting - GenericChildPerEntityParent, message extractors, cluster sharding abstraction, akka-reminders, and ITimeProvider. Supports both local testing and clustered production modes.

akka-net-aspire-configuration

Configure Akka.NET with .NET Aspire for local development and production deployments. Covers actor system setup, clustering, persistence, Akka.Management integration, and Aspire orchestration patterns.

akka-net-best-practices

Critical Akka.NET best practices including EventStream vs DistributedPubSub, supervision strategies, error handling, Props vs DependencyResolver, work distribution patterns, and cluster/local mode abstractions for testability.

akka-net-management

Akka.Management for cluster bootstrapping, service discovery (Kubernetes, Azure, Config), health checks, and dynamic cluster formation without static seed nodes.

akka-net-testing-patterns

Write unit and integration tests for Akka.NET actors using modern Akka.Hosting.TestKit patterns. Covers dependency injection, TestProbes, persistence testing, and actor interaction verification. Includes guidance on when to use traditional TestKit.

api-design

Design stable, compatible public APIs using extend-only design principles. Manage API compatibility, wire compatibility, and versioning for NuGet packages and distributed systems.

aspire-configuration

Configure Aspire AppHost to emit explicit app config via environment variables; keep app code free of Aspire clients and service discovery.

aspire-integration-testing

Write integration tests using .NET Aspire

aspire-service-defaults

Create a shared ServiceDefaults project for Aspire applications. Centralizes OpenTelemetry, health checks, resilience, and service discovery configuration across all services.

crap-analysis

Analyze code coverage and CRAP (Change Risk Anti-Patterns) scores to identify high-risk code. Use OpenCover format with ReportGenerator for Risk Hotspots showing cyclomatic complexity and untested code paths.

database-performance

Database access patterns for performance. Separate read/write models, avoid N+1 queries, use AsNoTracking, apply row limits, and never do application-side joins. Works with EF Core and Dapper.

dependency-injection-patterns

Organize DI registrations using IServiceCollection extension methods. Group related services into composable Add* methods for clean Program.cs and reusable configuration in tests.

dotnet-devcert-trust

Diagnose and fix .NET HTTPS dev certificate trust issues on Linux. Covers the full certificate lifecycle from generation to system CA bundle inclusion, with distro-specific guidance for Ubuntu, Fedora, Arch, and WSL2.

dotnet-local-tools

Managing local .NET tools with dotnet-tools.json for consistent tooling across development environments and CI/CD pipelines.

dotnet-project-structure

Modern .NET project structure including .slnx solution format, Directory.Build.props, central package management, SourceLink, version management with RELEASE_NOTES.md, and SDK pinning with global.json.

dotnet-slopwatch

Use Slopwatch to detect LLM reward hacking in .NET code changes. Run after every code modification to catch disabled tests, suppressed warnings, empty catch blocks, and other shortcuts that mask real problems.

efcore-patterns

Entity Framework Core best practices including NoTracking by default, query splitting for navigation collections, migration management, dedicated migration services, and common pitfalls to avoid.

ilspy-decompile

Understand implementation details of .NET code by decompiling assemblies. Use when you want to see how a .NET API works internally, inspect NuGet package source, view framework implementation, or understand compiled .NET binaries.

mailpit-integration

Test email sending locally using Mailpit with .NET Aspire. Captures all outgoing emails without sending them. View rendered HTML, inspect headers, and verify delivery in integration tests.

marketplace-publishing

Workflow for publishing skills and agents to the dotnet-skills Claude Code marketplace. Covers adding new content, updating plugin.json, validation, and release tagging.

Verwandte Skills