Aufbau einer wiederverwendbaren Ephemeral Execution Library
Im **Teil: Feuer und Don-' Ganz schön Vergessen**Wir haben die Theorie hinter der ephemeren Ausführung untersucht.- bounded.MSC2 private, debuggable async Workflows, die gerade genug erinnern, um nützlich zu sein und dann verdunsten.
Dieser Artikel verwandelt dieses Muster in eine wiederverwendbare Bibliothek, die Sie in ein beliebiges .-NET-Projekt fallen lassen können.
NUGET-!!!
Dies ist jetzt im meistlucid-.-Ephemerals Nuget-Paket auch mehr als 20, meistluzid, ., Ephemermuster und ', Atome, MSC3,.
Quelldateien
Der vollständige Quellcode ist in der meistlucid-.-Toms GitHub-Repository
Vor und nach
Hier ist das, was wir ersetzen '
// ❌ Before: Fire-and-forget black hole
_ = Task.Run(() => ProcessAsync(item));
// No visibility. No debugging. No idea if it worked.
// ❌ Or: Blocking everything
await ProcessAsync(item); // Hope you like waiting...
Und was bauen wir?
// ✅ After: Trackable, bounded, debuggable
await coordinator.EnqueueAsync(item);
// Instant visibility
Console.WriteLine($"Pending: {coordinator.PendingCount}");
Console.WriteLine($"Active: {coordinator.ActiveCount}");
Console.WriteLine($"Failed: {coordinator.TotalFailed}");
// Full operation history
var snapshot = coordinator.GetSnapshot();
var failures = coordinator.GetFailed();
Gleiche async-Ausführung – .- Vollständige Beobachtbarkeit –.-Keine gespeicherten Benutzerdaten
Schnellstart
Das gängigste Muster registriert einen Koordinator in DI und injiziert es.
// Program.cs
services.AddEphemeralWorkCoordinator<TranslationRequest>(
async (request, ct) => await TranslateAsync(request, ct),
new EphemeralOptions { MaxConcurrency = 8 });
// Your service
public class TranslationService(EphemeralWorkCoordinator<TranslationRequest> coordinator)
{
public async Task TranslateAsync(TranslationRequest request)
{
await coordinator.EnqueueAsync(request);
// Returns immediately - work happens in background
}
public object GetStatus() => new
{
pending = coordinator.PendingCount,
active = coordinator.ActiveCount,
completed = coordinator.TotalCompleted,
failed = coordinator.TotalFailed
};
}
Welche Variante brauche ich?
┌─────────────────────────────────────────────────────────────────┐
│ DECISION TREE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Processing a collection once? │
│ └─► EphemeralForEachAsync<T> (ParallelEphemeral.cs) │
│ │
│ Need a long-lived queue that accepts items over time? │
│ └─► EphemeralWorkCoordinator<T> │
│ │
│ Need per-entity ordering (user commands, tenant jobs)? │
│ └─► EphemeralKeyedWorkCoordinator<TKey, T> │
│ │
│ Need to capture results (fingerprints, summaries)? │
│ └─► EphemeralResultCoordinator<TInput, TResult> │
│ │
│ Need multiple coordinators with different configs? │
│ └─► IEphemeralCoordinatorFactory<T> (like IHttpClientFactory) │
│ │
│ Need dynamic concurrency adjustment at runtime? │
│ └─► Set EnableDynamicConcurrency = true, call SetMaxConcurrency│
│ │
└─────────────────────────────────────────────────────────────────┘
Das Konfigurationsobjekt
Von Ephemere Optionen:
public sealed class EphemeralOptions
{
// Concurrency control
public int MaxConcurrency { get; init; } = Environment.ProcessorCount;
public int MaxConcurrencyPerKey { get; init; } = 1;
public bool EnableDynamicConcurrency { get; init; } = false;
// Window management
public int MaxTrackedOperations { get; init; } = 200;
public TimeSpan? MaxOperationLifetime { get; init; } = TimeSpan.FromMinutes(5);
// Fair scheduling (keyed coordinator)
public bool EnableFairScheduling { get; init; } = false;
public int FairSchedulingThreshold { get; init; } = 10;
// Signal-reactive processing
public IReadOnlySet<string>? CancelOnSignals { get; init; }
public IReadOnlySet<string>? DeferOnSignals { get; init; }
public int MaxDeferAttempts { get; init; } = 10;
public TimeSpan DeferCheckInterval { get; init; } = TimeSpan.FromMilliseconds(100);
// Signal infrastructure
public SignalSink? Signals { get; init; }
public SignalConstraints? SignalConstraints { get; init; }
public Action<SignalEvent>? OnSignal { get; init; }
// Async signal handling
public Func<SignalEvent, CancellationToken, Task>? OnSignalAsync { get; init; }
public int MaxConcurrentSignalHandlers { get; init; } = 4;
public int MaxQueuedSignals { get; init; } = 1000;
// Observability
public Action<IReadOnlyCollection<EphemeralOperationSnapshot>>? OnSample { get; init; }
}
Schlüsselentscheidungen für die Gestaltung
- Maximale Übereinstimmung Standardwerte für die CPU-Zählung -, sinnvoll für CPU--, gebundene Arbeit ., für IM/, gebundenes Arbeiten ,, erhöhen es .,
- DynamischeKonkurrenz aktivieren ermöglicht die Einstellung der Laufzeit über
SetMaxConcurrency()Ein benutzerdefiniertes Gate anstelle vonSemaphoreSlim. - AbbrechenOnSignals-/-DeferOnSignale make coordinators signal---reaktive -- sie reagieren auf den Zustand des Umgebungssystems
*/?Weitere Informationen sind abrufbar unter: http://www.cordis.lu/ - AufSignal ist synchron, für async-lüfter, --out
SignalDispatcheroderAsyncSignalProcessorim Inneren des Handlers - Signalbeeinträchtigungen verhindert unendliche Signalschleifen mit Zyklusdetektion und Tiefengrenzwerte
Die Snapshot-Datensätze
public sealed record EphemeralOperationSnapshot(
long Id,
DateTimeOffset Started,
DateTimeOffset? Completed,
string? Key,
bool IsFaulted,
Exception? Error,
TimeSpan? Duration,
IReadOnlyList<string>? Signals = null,
bool IsPinned = false)
{
public bool HasSignal(string signal) => Signals?.Contains(signal) == true;
}
// For result-capturing coordinators
public sealed record EphemeralOperationSnapshot<TResult>(
long Id,
DateTimeOffset Started,
DateTimeOffset? Completed,
string? Key,
bool IsFaulted,
Exception? Error,
TimeSpan? Duration,
TResult? Result,
bool HasResult,
IReadOnlyList<string>? Signals = null,
bool IsPinned = false);
Das ist nur MetadatenBemerken Sie, was -' ist nicht Hier ist :.
- Keine Nutzlast
- Keine Eingabedaten
- Kein Benutzerinhalt
Gerade genug, um zu beantworten, was geschah, wenn, , und tat es funktionierte, ?" und nicht mehr.
Wie dies mit anderen Ansätzen verglichen wird
.-NET gibt Ihnen mehrere Möglichkeiten, parallele Arbeit zu tun. .. Hier ist '. Wie die Ephemere Bibliothek vergleicht.
Parallel für jedesAsync (.-NET
await Parallel.ForEachAsync(items,
new ParallelOptions { MaxDegreeOfParallelism = 4 },
async (item, ct) => await ProcessAsync(item, ct));
Das Beste fürEinfache parallele Verarbeitung von Sammlungen, in denen Sie nicht benötigen Sichtbarkeit
Was ihm fehlt:
- Keine Betriebsverfolgung
- Keine sequentielle Ausführung per--.
- Keine Sichtbarkeit in was-'
Ephemeral anwenden, wennSie benötigen Debugging ,/ Beobachtbarkeit , , , per , - key Bestellung , oder Signal , - , reaktive Verarbeitung ,
TPL-Datenfluss
var block = new ActionBlock<T>(
async item => await ProcessAsync(item),
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 4 });
foreach (var item in items)
block.Post(item);
block.Complete();
await block.Completion;
Das Beste fürKomplexe Datenflusspipelines mit Verzweigung – , – Zusammenführen von , – Batching – MKK3 –
Was es gut macht:
- Reiche Pipeline-Zusammensetzung Verkettungsblöcke zusammen
- Konstruiert in Batching, ,, transformierend in ,, Rundfunk
- Gebundene Kapazität mit rückseitigem Druck
TPL Dataflow verwenden, wennSie benötigen komplexe Pipeline-Topologien, bedingtes Routing, ).
Ephemeral anwenden, wennSie benötigen Operations-Tracking - einfachere API - , oder Signal - reaktive Koordination - .
System ,. , Threading , . , Channels
var channel = Channel.CreateBounded<T>(100);
// Producer
foreach (var item in items)
await channel.Writer.WriteAsync(item);
channel.Writer.Complete();
// Consumer (multiple workers)
var workers = Enumerable.Range(0, 4).Select(async _ =>
{
await foreach (var item in channel.Reader.ReadAllAsync())
await ProcessAsync(item);
});
await Task.WhenAll(workers);
Das Beste fürVerbrauchermuster, bei denen Sie beide Seiten kontrollieren
Was es gut macht:
- Ausgezeichnete Leistung
- Rückwärtsdruck des Drucks über geschränkte Kanäle
- Trennung von Erzeugern und Verbrauchern
Kanäle verwenden, wennSie bauen benutzerdefinierte Infrastruktur und benötigen maximale Kontrolle.
Ephemeral anwenden, wennSie möchten Betriebsverfolgung und Beobachtbarkeit ohne die Kesselplatte?
Polly
var policy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
await policy.ExecuteAsync(() => ProcessAsync(item));
Das Beste fürRessilienz-Politiken für einzelne Operationen
Polly verwenden, wennSie brauchen Widerstandsfähigkeit um individuelle Anrufe.
Ephemeral anwenden, wennSie müssen über viele Operationen mit Umgebungsbewusstsein koordiniert werden.
Kombiniere sieVerwenden Sie Polly im Inneren Ihres Ephemeren Arbeitskörpers für die Per---Belastbarkeit
MasseTransit-Massentransit-NServiceBus
Das Beste fürVerteilte Messaging-Dienste mit dauerhaften Warteschlangen
Nachrichtenbusse verwenden, wennArbeiten müssen Prozess-Neustarts überleben , , spannen mehrere Dienste , oder erfordern eine garantierte Lieferung ,. ,
Ephemeral anwenden, wennDie Arbeit ist in ,-' Prozess , ,' doesns , , und Sie wollen leichte Beobachtbarkeit , .'
Vergleichstabelle
| -Annäherung - Gebundene , | -Nachverfolgung , | -Per---Key-Signale - Selbstreinigung , --Reinigung | |||||
|---|---|---|---|---|---|---|---|
Parallel.ForEachAsync |
✅ | ❌T | ❌X | x❌ | N-/1-m- | h-m | |
| -TPL-Datenfluss | -✅-M | -❌- | -MSC6- | -MSSK8-MSZ9-MS K10-MSD11- Hoch | |||
| -Kanale , | , ✅ , | , ❌ , - | ,-❌ , – | , ❌ ," | , ❌, | Medium , | |
| , Polly, | , MSC2, MCSK3, MKSK4, M | , MNSK6, MSSK7, ❌, N/, A | , Low, MSV12 | ||||
| Hintergrunddienstleistungen , | , ❌ , | , ❌ , | , -❌ ,- | , –❌ , | ❌ ," | , Medium , | " |
| -MasseTransit/-NServiceBus | -✅- | -M✅-MSC6-✅-MSSK8-MSZ9-MS K10-MS-❌-MSG12-Hoch | |||||
| Ephemere Bibliothek | ✅ | ✅T | ✅- | ✅X✅8- | 1-MSC11 |
EphemeralFürjedenAsync-:-Die Eine---Hot-Version
// Simple parallel processing with tracking
await items.EphemeralForEachAsync(
async (item, ct) => await ProcessAsync(item, ct),
new EphemeralOptions { MaxConcurrency = 8 });
// With keyed execution (per-user sequential)
await commands.EphemeralForEachAsync(
cmd => cmd.UserId, // Key selector
async (cmd, ct) => await ExecuteCommandAsync(cmd, ct),
new EphemeralOptions
{
MaxConcurrency = 32,
MaxConcurrencyPerKey = 1 // Sequential per user
});
Warum schlüsselfertige Pipelines wichtig sind
Stellen Sie sich vor, Benutzerbefehle zu verarbeiten.
- Benutzer A sendet Befehle
- Benutzer B sendet Befehle
Ohne Keying könnten diese als : ausführen.
Mit MaxConcurrencyPerKey = 1:
- Benutzer-Befehle, die in der Reihenfolge ausgeführt werden
- Benutzer-Befehle, die in der Reihenfolge ausgeführt werden
- Aber A und B können parallel laufen
Das ist Per--'entity sequentiell ,,' global parallel Für Systeme, bei denen die Ordnung innerhalb einer Einheit von Bedeutung ist, kritisch
Der Arbeitskoordinator„:" Eine lange, lebendige Warteschlange
Von Ephemere ArbeitKoordinator:
await using var coordinator = new EphemeralWorkCoordinator<TranslationRequest>(
async (request, ct) => await TranslateAsync(request, ct),
new EphemeralOptions
{
MaxConcurrency = 8,
MaxTrackedOperations = 500,
EnableDynamicConcurrency = true // Allow runtime adjustment
});
// Enqueue items over time
await coordinator.EnqueueAsync(new TranslationRequest("Hello", "es"));
// Check status anytime
Console.WriteLine($"Pending: {coordinator.PendingCount}");
Console.WriteLine($"Active: {coordinator.ActiveCount}");
// Get snapshots
var snapshot = coordinator.GetSnapshot();
var running = coordinator.GetRunning();
var failed = coordinator.GetFailed();
var completed = coordinator.GetCompleted();
// Control flow
coordinator.Pause(); // Stop pulling new work
coordinator.Resume(); // Continue
// Adjust concurrency at runtime (requires EnableDynamicConcurrency)
coordinator.SetMaxConcurrency(16);
// Pin important operations to survive eviction
coordinator.Pin(operationId);
coordinator.Unpin(operationId);
coordinator.Evict(operationId);
// When done
coordinator.Complete();
await coordinator.DrainAsync();
Kontinuierliche Streams mit IAsyncEnumerable
await using var coordinator = EphemeralWorkCoordinator<Message>.FromAsyncEnumerable(
messageStream, // IAsyncEnumerable<Message>
async (msg, ct) => await ProcessMessageAsync(msg, ct),
new EphemeralOptions { MaxConcurrency = 16 });
await coordinator.DrainAsync();
Die Keyed-Koordinator-:-Per---Entity Pipelines
Von EphemeralKeyedWorkKoordinator:
await using var coordinator = new EphemeralKeyedWorkCoordinator<string, Command>(
cmd => cmd.UserId, // Key selector
async (cmd, ct) => await ExecuteCommandAsync(cmd, ct),
new EphemeralOptions
{
MaxConcurrency = 32,
MaxConcurrencyPerKey = 1, // Per-user sequential
EnableFairScheduling = true, // Prevent hot user starvation
FairSchedulingThreshold = 10 // Reject if user has 10+ pending
});
// TryEnqueue returns false if fair scheduling rejects
if (!coordinator.TryEnqueue(hotUserCommand))
{
await DeferCommandAsync(hotUserCommand);
}
// Per-key visibility
var pendingForUser = coordinator.GetPendingCountForKey("user-123");
var opsForUser = coordinator.GetSnapshotForKey("user-123");
Ergebnis: --Capturing-Koordinatoren
Von EphemeralResultKoordinator:
await using var coordinator = new EphemeralResultCoordinator<SessionInput, SessionResult>(
async (input, ct) =>
{
var fingerprint = await ComputeFingerprintAsync(input.Events, ct);
return new SessionResult(fingerprint, input.Events.Length);
},
new EphemeralOptions { MaxConcurrency = 16 });
await coordinator.EnqueueAsync(session);
coordinator.Complete();
await coordinator.DrainAsync();
// Get just the results (no metadata)
var results = coordinator.GetResults();
// Get snapshots with results + metadata
var snapshots = coordinator.GetSnapshot();
// Get base snapshots without results (privacy-safe)
var baseSnapshots = coordinator.GetBaseSnapshot();
// Filter by success/failure
var successful = coordinator.GetSuccessful();
var failed = coordinator.GetFailed();
Kontrolle der Übereinstimmung
Von ÜbereinstimmungGates-.-cs:
Die Bibliothek bietet zwei Concurrency-Kontrollmechanismen.
BehobenConcurrencyGate
- Gesichert von
SemaphoreSlim - Optimale Hot---Path-Leistung
- Zur Laufzeit nicht justierbar
Einstellbares ConcurrenzGate
- Benutzerdefinierte Implementierung mit
Queue<WaiterEntry> - Unterstützungen
UpdateLimit()zur Laufzeit - Aktiviert über
EnableDynamicConcurrency = true
// Dynamic concurrency adjustment
var coordinator = new EphemeralWorkCoordinator<T>(body,
new EphemeralOptions
{
MaxConcurrency = 4,
EnableDynamicConcurrency = true
});
// Later, based on system load:
coordinator.SetMaxConcurrency(16); // Scale up
coordinator.SetMaxConcurrency(2); // Scale down
Die Werksmuster-Koordinatoren
Wie IHttpClientFactorySie können benannte Konfigurationen registrieren.
// Registration
services.AddEphemeralWorkCoordinator<TranslationRequest>("fast",
async (request, ct) => await FastTranslateAsync(request, ct),
new EphemeralOptions { MaxConcurrency = 32 });
services.AddEphemeralWorkCoordinator<TranslationRequest>("accurate",
async (request, ct) => await AccurateTranslateAsync(request, ct),
new EphemeralOptions { MaxConcurrency = 4 });
// Usage
public class TranslationService(IEphemeralCoordinatorFactory<TranslationRequest> factory)
{
private readonly EphemeralWorkCoordinator<TranslationRequest> _fast =
factory.CreateCoordinator("fast");
private readonly EphemeralWorkCoordinator<TranslationRequest> _accurate =
factory.CreateCoordinator("accurate");
}
Betriebsgarantien
- Der gleiche Name ist dieselbe Instanz. Anrufen
CreateCoordinator("fast")zweimal gibt den gleichen Koordinator zurück - Verschiedene Namen, verschiedene Instanzen -
"fast"und"accurate"separate Koordinatoren erhalten - Faule Schöpfung Die Koordinatoren werden nur erstellt, wenn sie zuerst angefordert werden.
- Validierung der Konfiguration Die Anforderung eines nicht registrierten Namens wirft einen hilfreichen Fehler auf
Signalabfrage-API
Alle Koordinatoren stellen optimierte Signalabfragemethoden zur Verfügung
// Get all signals
var signals = coordinator.GetSignals();
// Filter by key (zero-allocation)
var userSignals = coordinator.GetSignalsByKey("user-123");
// Filter by time range
var recentSignals = coordinator.GetSignalsSince(DateTimeOffset.UtcNow.AddMinutes(-5));
var rangeSignals = coordinator.GetSignalsByTimeRange(from, to);
// Filter by signal name or pattern
var rateSignals = coordinator.GetSignalsByName("rate-limit");
var httpSignals = coordinator.GetSignalsByPattern("http.*");
// Check existence (short-circuits on first match)
if (coordinator.HasSignal("rate-limit"))
await ThrottleAsync();
if (coordinator.HasSignalMatching("error.*"))
await AlertAsync();
// Count signals efficiently (no allocation)
var totalSignals = coordinator.CountSignals();
var errorCount = coordinator.CountSignals("error");
var httpCount = coordinator.CountSignalsMatching("http.*");
Produktionsoptimierungen
Schnelle ID-Erstellung
Von EphemeralIdGenerator-.-cs:
internal static class EphemeralIdGenerator
{
private static long _counter;
private static readonly long _processStart = Environment.TickCount64;
private static readonly int _processId = Environment.ProcessId;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static long NextId()
{
var counter = Interlocked.Increment(ref _counter);
// Combine counter with process-unique seed
Span<byte> buffer = stackalloc byte[24];
BitConverter.TryWriteBytes(buffer, _processStart);
BitConverter.TryWriteBytes(buffer.Slice(8), _processId);
BitConverter.TryWriteBytes(buffer.Slice(16), counter);
return unchecked((long)XxHash64.HashToUInt64(buffer));
}
}
- Verteilungsfrei Verwendungen
stackalloc) - Thread---sicher Verwendungen
Interlocked.Increment) - Einzigartig über alle Prozesse hinweg Einschließlich Prozess ID.).
- Nicht-- sequenzielle Der Zähler wird von (hash diffundiert
Speicher - Sichere Langzeit-- - Lived Operation
Die Koordinatoren speichern Task Referenzen - gerade Zähler , :
private int _activeTaskCount;
private readonly TaskCompletionSource _drainTcs;
// In ExecuteItemAsync:
finally
{
// Signal drain when last task completes AND channel iteration is done
if (Interlocked.Decrement(ref _activeTaskCount) == 0 &&
Volatile.Read(ref _channelIterationComplete))
{
_drainTcs.TrySetResult();
}
}
Bereinigung der Schlüsselsperre
Der Keyed-Koordinator säubert automatisch den Leerlauf per.-.key Semaphores.
private sealed class KeyLock(SemaphoreSlim gate, int maxCount)
{
public SemaphoreSlim Gate { get; } = gate;
public int MaxCount { get; } = maxCount;
public long LastUsedTicks = Environment.TickCount64;
}
// Cleanup runs periodically, removes locks idle > 60 seconds
Vollständiges Beispiel
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Named coordinators
builder.Services.AddEphemeralWorkCoordinator<TranslationRequest>("fast",
async (req, ct) => await FastTranslateAsync(req, ct),
new EphemeralOptions { MaxConcurrency = 16 });
// Keyed coordinator for per-user commands
builder.Services.AddEphemeralKeyedWorkCoordinator<string, UserCommand>("commands",
cmd => cmd.UserId,
sp =>
{
var handler = sp.GetRequiredService<ICommandHandler>();
return async (cmd, ct) => await handler.HandleAsync(cmd, ct);
},
new EphemeralOptions
{
MaxConcurrency = 32,
MaxConcurrencyPerKey = 1,
EnableFairScheduling = true,
CancelOnSignals = new HashSet<string> { "system-overload" }
});
var app = builder.Build();
// Controller
[ApiController]
[Route("api")]
public class WorkController : ControllerBase
{
private readonly EphemeralWorkCoordinator<TranslationRequest> _translator;
private readonly EphemeralKeyedWorkCoordinator<string, UserCommand> _commands;
public WorkController(
IEphemeralCoordinatorFactory<TranslationRequest> translationFactory,
IEphemeralKeyedCoordinatorFactory<string, UserCommand> commandFactory)
{
_translator = translationFactory.CreateCoordinator("fast");
_commands = commandFactory.CreateCoordinator("commands");
}
[HttpPost("translate")]
public async Task<IActionResult> Translate([FromBody] TranslationRequest request)
{
await _translator.EnqueueAsync(request);
return Ok(new { pending = _translator.PendingCount });
}
[HttpPost("command")]
public IActionResult SubmitCommand([FromBody] UserCommand command)
{
if (!_commands.TryEnqueue(command))
return StatusCode(429, "Too many pending commands for this user");
return Ok();
}
[HttpGet("status")]
public IActionResult GetStatus() => Ok(new
{
translator = new
{
pending = _translator.PendingCount,
active = _translator.ActiveCount,
completed = _translator.TotalCompleted,
failed = _translator.TotalFailed,
hasRateLimit = _translator.HasSignal("rate-limit")
},
commands = new
{
pending = _commands.PendingCount,
active = _commands.ActiveCount,
errorCount = _commands.CountSignalsMatching("error.*")
}
});
}
Schlußfolgerung
Wir bauten eine komplette ephemere Ausführungsbibliothek mit
EphemeralForEachAsyncParallele Verarbeitung mit TrackingEphemeralWorkCoordinatorDauerhafte WarteschlangenEphemeralKeyedWorkCoordinatorPERSÖNLICHE ZEITSCHRIFTEphemeralResultCoordinatorErgebnis: -- Fabrikmuster Benannte Konfigurationen wie
IHttpClientFactory - Dynamische Übereinstimmung Laufzeitanpassung der Parallelität
- Signalinfrastruktur Signal-Emission und Abfragen
Das Muster sitzt in einem süßen Fleck.
- Beobachtbarer als
Parallel.ForEachAsync - Einfacher als TPL-Datenfluss
- Mehr integriert als rohe Kanäle
- Datenschutz - Sicherheit durch Design
Feuer, ... und Don-'nicht ganz vergessen
Verknüpfungen
- Teil: Feuer und Don-' Ganz schön Vergessen Die Theorie und das Muster
- Teil 3:-Ephemerale Signale Aus Atomen wird ein Sensornetzwerk
- SemaphoreSlim Dokumentation
- System ,. , Threading , . , Channels
- TPL-Datenfluss
- IHttpClientFertigungsmuster