csharp-async-patterns
npx skills add https://github.com/icartsh/icartsh_plugin --skill csharp-async-patterns
Agent 安装分布
Skill 文档
C# Async Patterns
async/await, Task, ValueTask, async streams ë° cancellation í¨í´ì ì¬ì©íì¬ C# ë¹ë기 íë¡ê·¸ëë°ì ë§ì¤í°í©ëë¤. ì´ SKILLì ë°ìì±ì´ ë°ì´ëê³ íì¥ì´ ì©ì´í ì í리ì¼ì´ì ì 구ì¶í기 ìí´ C# 8-12ì 모ë ë¹ë기 í¨í´ì ë¤ë£¹ëë¤.
Async/Await Fundamentals
async/await í¨í´ì ë기 ì½ëì²ë¼ ë³´ì´ê³ ëìíë ë¹ë기 ì½ë를 ìì±íë ê°ë¨í ë°©ë²ì ì ê³µí©ëë¤.
Basic Async Method
public async Task<string> FetchDataAsync(string url)
{
using var client = new HttpClient();
string result = await client.GetStringAsync(url);
return result;
}
// ë¹ë기 ë©ìë í¸ì¶
public async Task ProcessAsync()
{
string data = await FetchDataAsync("https://api.example.com/data");
Console.WriteLine(data);
}
Async Method Signature Rules
// â
ì¬ë°ë¦ - Task ë°í
public async Task ProcessDataAsync()
{
await Task.Delay(1000);
}
// â
ì¬ë°ë¦ - Task<T> ë°í
public async Task<int> CalculateAsync()
{
await Task.Delay(1000);
return 42;
}
// â ï¸ ì´ë²¤í¸ í¸ë¤ë¬ ì ì© - void ë°í
public async void Button_Click(object sender, EventArgs e)
{
await ProcessDataAsync();
}
// â ìëª»ë¨ - asyncê° ìëì§ë§ Task ë°í
public Task WrongAsync()
{
// async를 ì¬ì©íê±°ë Task.FromResult를 ì¬ì©í´ì¼ í¨
return Task.CompletedTask;
}
Task and Task
Taskë ë¹ë기 ìì ì ëíë ëë¤. Taskë ê°ì ë°ííë ìì ì ëíë ëë¤.
Creating Tasks
// CPU ì§ì½ì ìì
ì ìí Task.Run
public async Task<int> CalculateSumAsync(int[] numbers)
{
return await Task.Run(() => numbers.Sum());
}
// ì´ë¯¸ ê³ì°ë ê°ì ìí Task.FromResult
public Task<string> GetCachedValueAsync(string key)
{
if (_cache.TryGetValue(key, out var value))
{
return Task.FromResult(value);
}
return FetchFromDatabaseAsync(key);
}
// void ë¹ë기 ë©ìë를 ìí Task.CompletedTask
public Task ProcessIfNeededAsync(bool condition)
{
if (!condition)
{
return Task.CompletedTask;
}
return DoActualWorkAsync();
}
Task Composition
public async Task<Result> ProcessOrderAsync(Order order)
{
// ìì°¨ì ì¤í (Sequential execution)
await ValidateOrderAsync(order);
await ChargePaymentAsync(order);
await ShipOrderAsync(order);
return new Result { Success = true };
}
public async Task<Result> ProcessOrderParallelAsync(Order order)
{
// ë³ë ¬ ì¤í (Parallel execution)
var validationTask = ValidateOrderAsync(order);
var inventoryTask = CheckInventoryAsync(order);
var pricingTask = CalculatePricingAsync(order);
await Task.WhenAll(validationTask, inventoryTask, pricingTask);
return new Result
{
IsValid = await validationTask,
InStock = await inventoryTask,
Price = await pricingTask
};
}
ValueTask and ValueTask
ValueTaskë ê²°ê³¼ê° ë기ì ì¼ë¡ ì¬ì© ê°ë¥í ê²½ì°ê° ë§ì ë ì¬ì©íë ì±ë¥ ìµì í ìë¨ì ëë¤.
When to Use ValueTask
public class CachedRepository
{
private readonly Dictionary<int, User> _cache = new();
private readonly IDatabase _database;
// â
ValueTask ì¬ì©ì´ ì ì í ì¬ë¡ - ìºììì ë기ì ì¼ë¡ ë°íëë ê²½ì°ê° ë§ì
public ValueTask<User> GetUserAsync(int id)
{
if (_cache.TryGetValue(id, out var user))
{
return ValueTask.FromResult(user);
}
return new ValueTask<User>(FetchUserFromDatabaseAsync(id));
}
private async Task<User> FetchUserFromDatabaseAsync(int id)
{
var user = await _database.QueryAsync<User>(id);
_cache[id] = user;
return user;
}
}
ValueTask Best Practices
public class BufferedReader
{
private readonly byte[] _buffer = new byte[4096];
private int _position;
private int _length;
// Hot path ìµì í를 ìí ValueTask
public async ValueTask<byte> ReadByteAsync()
{
if (_position < _length)
{
// ë기 ê²½ë¡ - í ë¹ ìì (No allocation)
return _buffer[_position++];
}
// ë¹ë기 ê²½ë¡ - ë°ì´í° ì¶ê° ì½ê¸°
await FillBufferAsync();
return _buffer[_position++];
}
private async Task FillBufferAsync()
{
_length = await _stream.ReadAsync(_buffer);
_position = 0;
}
}
// â ï¸ ValueTask ê·ì¹
public async Task ConsumeValueTaskAsync()
{
var reader = new BufferedReader();
// â
ì¬ë°ë¦ - í ë²ë§ await
byte b = await reader.ReadByteAsync();
// â ìëª»ë¨ - ValueTask를 ì ì¥íì§ ë§ì¸ì
var task = reader.ReadByteAsync();
await task; // ì ì¬ì ì´ì ë°ì ê°ë¥
// â ìëª»ë¨ - ì¬ë¬ ë² await íì§ ë§ì¸ì
var vt = reader.ReadByteAsync();
await vt;
await vt; // ì ë íì§ ë§ì¸ì
}
Async Void vs Async Task
async void (ëë¬¼ê² ë°ì)ì async Task (ê±°ì íì ì¬ì©)를 ì¸ì ì¬ì©í ì§ ì´í´í©ëë¤.
The Async Void Problem
// â ëì¨ - await ë¶ê°, ìì¸ ì²ë¦¬ ì ë¨
public async void ProcessDataBadAsync()
{
await Task.Delay(1000);
throw new Exception("Unhandled!"); // ì± í¬ëì ë°ì
}
// â
ì¢ì - await ê°ë¥, ìì¸ ì²ë¦¬ ê°ë¥
public async Task ProcessDataGoodAsync()
{
await Task.Delay(1000);
throw new Exception("Handled!"); // catch ê°ë¥
}
// ì¬ì© ìì
public async Task CallerAsync()
{
try
{
// async voidë await ë¶ê°
ProcessDataBadAsync(); // Fire and forget - ìíí¨
// async Taskë await ê°ë¥
await ProcessDataGoodAsync(); // ì¬ê¸°ì ìì¸ catchë¨
}
catch (Exception ex)
{
Console.WriteLine($"Caught: {ex.Message}");
}
}
The Only Valid Use of Async Void
// â
ì´ë²¤í¸ í¸ë¤ë¬ - ì ì¼íê² íì©ëë ì¬ë¡
public partial class MainWindow : Window
{
public async void SaveButton_Click(object sender, RoutedEventArgs e)
{
try
{
await SaveDataAsync();
MessageBox.Show("Saved successfully!");
}
catch (Exception ex)
{
MessageBox.Show($"Error: {ex.Message}");
}
}
private async Task SaveDataAsync()
{
await _repository.SaveAsync(_data);
}
}
ConfigureAwait(false)
ë¼ì´ë¸ë¬ë¦¬ ì½ëìì ì±ë¥ì ìí´ synchronization context 캡ì²ë¥¼ ì ì´í©ëë¤.
Understanding ConfigureAwait
// ë¼ì´ë¸ë¬ë¦¬ ì½ë - ConfigureAwait(false) ì¬ì©
public class DataService
{
public async Task<Data> GetDataAsync(int id)
{
// ConfigureAwait(false) - 컨í
ì¤í¸ë¥¼ 캡ì²íì§ ìì
var json = await _httpClient.GetStringAsync($"/api/data/{id}")
.ConfigureAwait(false);
var data = await DeserializeAsync(json)
.ConfigureAwait(false);
return data;
}
}
// UI ì½ë - ConfigureAwait(false) ì¬ì© ê¸ì§
public class ViewModel
{
public async Task LoadDataAsync()
{
var data = await _dataService.GetDataAsync(42);
// ì¬ê¸°ì UI 컨í
ì¤í¸ê° íìí¨
this.DataProperty = data; // UI ì
ë°ì´í¸
}
}
ConfigureAwait Patterns
public class AsyncLibrary
{
// â
ConfigureAwait(false)를 ì¬ì©í ë¼ì´ë¸ë¬ë¦¬ ë©ìë
public async Task<Result> ProcessAsync(string input)
{
var step1 = await Step1Async(input).ConfigureAwait(false);
var step2 = await Step2Async(step1).ConfigureAwait(false);
var step3 = await Step3Async(step2).ConfigureAwait(false);
return step3;
}
// â
ASP.NET Core - ì´ëìë ConfigureAwait(false) ìì í¨
[HttpGet]
public async Task<IActionResult> GetData(int id)
{
// ASP.NET Coreìë synchronization contextê° ìì
var data = await _repository.GetAsync(id).ConfigureAwait(false);
return Ok(data);
}
}
CancellationToken Patterns
ì¤ë ì¤íëë ìì ì ëí ì ì í ì·¨ì½ì ì§ì.
Basic Cancellation
public async Task<List<Result>> ProcessItemsAsync(
IEnumerable<Item> items,
CancellationToken cancellationToken = default)
{
var results = new List<Result>();
foreach (var item in items)
{
// ì·¨ì ìì² íì¸
cancellationToken.ThrowIfCancellationRequested();
var result = await ProcessItemAsync(item, cancellationToken);
results.Add(result);
}
return results;
}
// Timeoutê³¼ í¨ê» ì¬ì©
public async Task<List<Result>> ProcessWithTimeoutAsync(IEnumerable<Item> items)
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
try
{
return await ProcessItemsAsync(items, cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation timed out");
throw;
}
}
Advanced Cancellation Patterns
public class BackgroundProcessor
{
private CancellationTokenSource? _cts;
public async Task StartAsync()
{
_cts = new CancellationTokenSource();
await ProcessLoopAsync(_cts.Token);
}
public void Stop()
{
_cts?.Cancel();
}
private async Task ProcessLoopAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
await ProcessBatchAsync(cancellationToken);
await Task.Delay(1000, cancellationToken);
}
catch (OperationCanceledException)
{
// ì·¨ì ì ììëë ìí©
break;
}
}
}
// ì°ê²°ë cancellation tokens (Linked cancellation tokens)
public async Task ProcessWithMultipleTokensAsync(
CancellationToken userToken,
CancellationToken systemToken)
{
using var linkedCts = CancellationTokenSource
.CreateLinkedTokenSource(userToken, systemToken);
await DoWorkAsync(linkedCts.Token);
}
}
Async Streams (IAsyncEnumerable)
IAsyncEnumerable를 ì¬ì©íì¬ ë¹ë기ì ì¼ë¡ ë°ì´í°ë¥¼ ì¤í¸ë¦¬ë°í©ëë¤ (C# 8+).
Basic Async Streams
public async IAsyncEnumerable<LogEntry> ReadLogsAsync(
string filePath,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await using var stream = File.OpenRead(filePath);
using var reader = new StreamReader(stream);
string? line;
while ((line = await reader.ReadLineAsync(cancellationToken)) != null)
{
if (TryParseLog(line, out var entry))
{
yield return entry;
}
}
}
// ë¹ë기 ì¤í¸ë¦¼ ìë¹
public async Task ProcessLogsAsync(string filePath)
{
await foreach (var log in ReadLogsAsync(filePath))
{
Console.WriteLine($"{log.Timestamp}: {log.Message}");
}
}
Advanced Async Stream Patterns
public class DataStreamProcessor
{
// íí°ë§ì´ í¬í¨ë ë¹ë기 ì¤í¸ë¦¼
public async IAsyncEnumerable<Event> GetEventsAsync(
DateTime startDate,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
int page = 0;
while (true)
{
var events = await FetchPageAsync(page++, cancellationToken);
if (events.Count == 0)
yield break;
foreach (var evt in events.Where(e => e.Date >= startDate))
{
yield return evt;
}
}
}
// ë¹ë기 ì¤í¸ë¦¼ì ëí LINQ ì¤íì¼ ìì
public async IAsyncEnumerable<TResult> SelectAsync<TSource, TResult>(
IAsyncEnumerable<TSource> source,
Func<TSource, TResult> selector)
{
await foreach (var item in source)
{
yield return selector(item);
}
}
// ë¹ë기 ì¤í¸ë¦¼ ë²í¼ë§ (Buffering)
public async IAsyncEnumerable<List<T>> BufferAsync<T>(
IAsyncEnumerable<T> source,
int bufferSize)
{
var buffer = new List<T>(bufferSize);
await foreach (var item in source)
{
buffer.Add(item);
if (buffer.Count >= bufferSize)
{
yield return buffer;
buffer = new List<T>(bufferSize);
}
}
if (buffer.Count > 0)
{
yield return buffer;
}
}
}
Parallel Async Operations
ì¬ë¬ ë¹ë기 ìì ì ëìì ì¤íí©ëë¤.
Task.WhenAll and Task.WhenAny
public async Task<Summary> GetDashboardDataAsync()
{
// 모ë ìì
ì ëìì ìì
var userTask = GetUserDataAsync();
var ordersTask = GetOrdersAsync();
var analyticsTask = GetAnalyticsAsync();
// 모ë ìë£ë ëê¹ì§ ë기
await Task.WhenAll(userTask, ordersTask, analyticsTask);
return new Summary
{
User = await userTask,
Orders = await ordersTask,
Analytics = await analyticsTask
};
}
// ì¼ë¶ ì¤í¨ ì²ë¦¬ (Partial failures)
public async Task<Results> ProcessWithPartialFailuresAsync()
{
var tasks = new[]
{
ProcessTask1Async(),
ProcessTask2Async(),
ProcessTask3Async()
};
await Task.WhenAll(tasks.Select(async t =>
{
try
{
await t;
}
catch (Exception ex)
{
// ë¡ê·¸ë¥¼ ë¨ê¸°ë throw íì§ ìì
Console.WriteLine($"Task failed: {ex.Message}");
}
}));
// ì±ê³µí ê²°ê³¼ ìì§
var results = tasks
.Where(t => t.IsCompletedSuccessfully)
.Select(t => t.Result)
.ToList();
return new Results { Successful = results };
}
Task.WhenAny for Timeouts and Racing
public async Task<T> WithTimeoutAsync<T>(Task<T> task, TimeSpan timeout)
{
var delayTask = Task.Delay(timeout);
var completedTask = await Task.WhenAny(task, delayTask);
if (completedTask == delayTask)
{
throw new TimeoutException("Operation timed out");
}
return await task;
}
// ì¬ë¬ ìì¤ ê° ë ì´ì± (Racing multiple sources)
public async Task<Data> GetFastestDataAsync()
{
var primaryTask = GetFromPrimaryAsync();
var secondaryTask = GetFromSecondaryAsync();
var cacheTask = GetFromCacheAsync();
var completedTask = await Task.WhenAny(primaryTask, secondaryTask, cacheTask);
return await completedTask;
}
// Throttled parallel processing (ëìì± ì í ë³ë ¬ ì²ë¦¬)
public async Task<List<Result>> ProcessWithThrottlingAsync(
IEnumerable<Item> items,
int maxConcurrency)
{
var semaphore = new SemaphoreSlim(maxConcurrency);
var tasks = items.Select(async item =>
{
await semaphore.WaitAsync();
try
{
return await ProcessItemAsync(item);
}
finally
{
semaphore.Release();
}
});
return (await Task.WhenAll(tasks)).ToList();
}
Exception Handling in Async Code
ë¹ë기 ë©ìëì ëí ì ì í ìì¸ ì²ë¦¬ í¨í´.
Basic Exception Handling
public async Task<Result> ProcessWithErrorHandlingAsync()
{
try
{
var data = await FetchDataAsync();
return await ProcessDataAsync(data);
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Network error occurred");
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "Unexpected error occurred");
return Result.Failed(ex.Message);
}
}
// Task.WhenAllê³¼ í¨ê» ì¬ì©íë ìì¸ ì²ë¦¬
public async Task ProcessMultipleAsync()
{
var tasks = new[] { Task1Async(), Task2Async(), Task3Async() };
try
{
await Task.WhenAll(tasks);
}
catch (Exception ex)
{
// 첫 ë²ì§¸ ìì¸ë§ throwë¨
_logger.LogError(ex, "At least one task failed");
// 모ë ìì¸ë¥¼ ê°ì ¸ì¤ë ¤ë©´:
var exceptions = tasks
.Where(t => t.IsFaulted)
.Select(t => t.Exception)
.ToList();
foreach (var exception in exceptions)
{
_logger.LogError(exception, "Task failed");
}
}
}
AggregateException Handling
public async Task HandleAllExceptionsAsync()
{
var tasks = Enumerable.Range(1, 10)
.Select(i => ProcessItemAsync(i))
.ToArray();
try
{
await Task.WhenAll(tasks);
}
catch
{
// 모ë ìì¸ ì¡°ì¬
var aggregateException = new AggregateException(
tasks.Where(t => t.IsFaulted)
.SelectMany(t => t.Exception?.InnerExceptions ?? Array.Empty<Exception>())
);
aggregateException.Handle(ex =>
{
if (ex is HttpRequestException)
{
_logger.LogWarning(ex, "Network error - retrying");
return true; // ì²ë¦¬ë¨ (Handled)
}
return false; // ë¤ì throw (Rethrow)
});
}
}
Deadlock Prevention
ë¹ë기 ì½ëìì íí ë°ìíë ë°ëë½ ìí©ì í¼í©ëë¤.
Common Deadlock Patterns
// â DEADLOCK - ë¹ë기 ì½ëìì blocking ë°ì
public void DeadlockExample()
{
// UI ëë ASP.NET 컨í
ì¤í¸ìì ë°ëë½ ë°ì
var result = GetDataAsync().Result;
// ì´ê² ëí ë°ëë½ ë°ì ê°ë¥
GetDataAsync().Wait();
}
// â
ì¬ë°ë¦ - ëê¹ì§ ë¹ë기 ì ì§ (async all the way)
public async Task CorrectExample()
{
var result = await GetDataAsync();
}
// â
ì¬ë°ë¦ - ë¼ì´ë¸ë¬ë¦¬ ì½ëìì ConfigureAwait(false) ì¬ì©
public async Task<Data> LibraryMethodAsync()
{
var data = await FetchAsync().ConfigureAwait(false);
return ProcessData(data);
}
Avoiding Deadlocks
public class DeadlockFreeService
{
// â
ëê¹ì§ ë¹ë기 ì ì§
public async Task<Result> ProcessAsync()
{
var data = await GetDataAsync();
var processed = await ProcessDataAsync(data);
return processed;
}
// â
ë¶ëì´íê² block í´ì¼ íë¤ë©´ Task.Run ì¬ì©
public Result ProcessSync()
{
return Task.Run(async () => await ProcessAsync()).GetAwaiter().GetResult();
}
// â
ë¹ë기 disposal ì¬ì© (Async disposal)
public async Task UseResourceAsync()
{
await using var resource = new AsyncDisposableResource();
await resource.ProcessAsync();
}
}
Async in ASP.NET Core
ASP.NET Core ì í리ì¼ì´ì ì ë¹ë기 ì½ë ëª¨ë² ì¬ë¡.
Controller Async Patterns
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IProductRepository _repository;
// â
Async ì¡ì
ë©ìë
[HttpGet("{id}")]
public async Task<ActionResult<Product>> GetProduct(
int id,
CancellationToken cancellationToken)
{
var product = await _repository.GetByIdAsync(id, cancellationToken);
if (product == null)
return NotFound();
return Ok(product);
}
[HttpPost]
public async Task<ActionResult<Product>> CreateProduct(
[FromBody] CreateProductRequest request,
CancellationToken cancellationToken)
{
var product = await _repository.CreateAsync(request, cancellationToken);
return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
}
// â
IAsyncEnumerableì ì¬ì©í ìëµ ì¤í¸ë¦¬ë°
[HttpGet("stream")]
public async IAsyncEnumerable<Product> StreamProducts(
[EnumeratorCancellation] CancellationToken cancellationToken)
{
await foreach (var product in _repository.GetAllStreamAsync(cancellationToken))
{
yield return product;
}
}
}
Background Services
public class DataProcessorService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<DataProcessorService> _logger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Data processor service starting");
while (!stoppingToken.IsCancellationRequested)
{
try
{
await ProcessDataBatchAsync(stoppingToken);
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
catch (OperationCanceledException)
{
// ì¤ì§ ì ììëë ìí©
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing data batch");
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
}
}
_logger.LogInformation("Data processor service stopped");
}
private async Task ProcessDataBatchAsync(CancellationToken cancellationToken)
{
using var scope = _serviceProvider.CreateScope();
var repository = scope.ServiceProvider.GetRequiredService<IDataRepository>();
await repository.ProcessBatchAsync(cancellationToken);
}
}
Best Practices
- Async All the Way: .Resultë .Wait()를 ì¬ì©íì¬ ë¹ë기 ì½ë를 block íì§ ë§ì¸ì.
- Use CancellationToken: ì¤ë ì¤íëë ìì ìë íì CancellationTokenì ë°ëë¡ íì¸ì.
- ConfigureAwait in Libraries: ë¼ì´ë¸ë¬ë¦¬ ì½ëììë ConfigureAwait(false)를 ì¬ì©íì¸ì.
- Avoid Async Void: ì´ë²¤í¸ í¸ë¤ë¬ì©ì¼ë¡ë§ async void를 ì¬ì©íì¸ì.
- Return Task Directly: ê°ë¥íë©´ await ìì´ Task를 ì§ì ë°ííì¸ì.
- Use ValueTask for Hot Paths: ì주 í¸ì¶ëê±°ë ë기ì ì¼ë¡ ì¤íëë ê²½ì°ê° ë§ì ë©ìëìë ValueTask를 ê³ ë ¤íì¸ì.
- Handle All Exceptions: ë¹ë기 ë©ìëììë íì ìì¸ë¥¼ ì²ë¦¬íì¸ì.
- Don’t Mix Blocking and Async: íëì í¸ì¶ ì²´ì¸ìë íëì í¨ë¬ë¤ìë§ ì ííì¸ì.
- Dispose Async Resources: IAsyncDisposableìë await usingì ì¬ì©íì¸ì.
- Test with Cancellation: ì·¨ìê° ì¬ë°ë¥´ê² ìëíëì§ í ì¤í¸íì¸ì.
Common Pitfalls
- Blocking on Async Code: .Resultë .Wait() ì¬ì©ì ë°ëë½ì ì ë°í©ëë¤.
- Forgetting ConfigureAwait: ë¼ì´ë¸ë¬ë¦¬ìì ì±ë¥ 문ì 를 ì¼ì¼í¬ ì ììµëë¤.
- Async Void Methods: awaitê° ë¶ê°ë¥íë©° ìì¸ë¥¼ ì¼ì¼ë²ë¦½ëë¤.
- Not Handling Cancellation: CancellationToken íë¼ë¯¸í°ë¥¼ 무ìíë ê².
- Over-using Task.Run: ì´ë¯¸ ë¹ëê¸°ì¸ ì½ë를 Task.Runì¼ë¡ ê°ì¸ì§ ë§ì¸ì.
- Capturing Context Unnecessarily: 컨í ì¤í¸ê° íì ìë ìí©ìì 리ìì¤ë¥¼ ëë¹í©ëë¤.
- Fire and Forget: await ìì´ ë¹ë기 ìì ì ììíë ê².
- Mixing Sync and Async: í¼ëì ì¼ê¸°íê³ ì ì¬ì ì¸ ë°ëë½ì ë§ëëë¤.
- Not Using ValueTask Correctly: ValueTask를 ì¬ë¬ ë² await íë ê².
- Ignoring Exceptions in Task.WhenAll: 첫 ë²ì§¸ ìì¸ë§ catch íë ê².
When to Use
ë¤ìì ìíí ë ì´ SKILLì ì¬ì©í©ëë¤:
- C#ìì ë¹ë기 ì½ë ìì±
- I/O ë°ì´ë ìì 구í (ë°ì´í°ë² ì´ì¤, ë¤í¸ìí¬, íì¼ ìì¤í )
- ë°ìí UI ì í리ì¼ì´ì 구ì¶
- íì¥ ê°ë¥í ì¹ ìë¹ì¤ 구ì¶
- ë°ì´í° ì¤í¸ë¦¼ ì²ë¦¬
- ì·¨ì ì§ì(Cancellation support) 구í
- ValueTask를 íµí ë¹ë기 ì±ë¥ ìµì í
- ë³ë ¬ ë¹ë기 ìì ì²ë¦¬
- ë¹ë기 ì½ëì ë°ëë½ ë°©ì§
- ASP.NET Core ë¹ë기 í¨í´ ìì