用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/Objective-Arts/lens-dist --skill async命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | async |
| description | Async/await and concurrency patterns |
Stephen Cleary's core belief: Async is about scalability, not performance. It frees threads to do other work while waiting. Get the patterns right, and async code is as simple as sync code.
"Async all the way down. Don't block on async code."
The moment you block (.Result, .Wait()), you lose all benefits and risk deadlocks.
Async must propagate through the entire call stack.
Not this:
// WRONG: Blocking on async
public string GetData()
{
return GetDataAsync().Result; // DEADLOCK RISK!
}
public string GetData()
{
return GetDataAsync().GetAwaiter().GetResult(); // Still blocking
}
This:
// RIGHT: Async all the way up
public async Task<string> GetDataAsync()
{
return await httpClient.GetStringAsync(url);
}
// Caller is also async
public async Task ProcessAsync()
{
var data = await GetDataAsync();
// ...
}
Why blocking deadlocks:
In UI/ASP.NET contexts, await captures a synchronization context. When you block with .Result, the thread waits. When the async operation completes, it tries to resume on that same thread—which is blocked waiting. Deadlock.
async void is fire-and-forget with no error handling.
Not this:
// WRONG: Exceptions vanish, caller can't await
async void ProcessData()
{
await Task.Delay(1000);
throw new Exception("Oops"); // Crashes the process!
}
This:
// RIGHT: Return Task for error propagation
async Task ProcessDataAsync()
{
await Task.Delay(1000);
throw new Exception("Oops"); // Caller can catch
}
The one exception: Event handlers must be async void:
// Event handlers - the only valid async void
private async void Button_Click(object sender, EventArgs e)
{
try
{
await DoWorkAsync();
}
catch (Exception ex)
{
// Handle here since caller can't
ShowError(ex.Message);
}
}
Every async operation should be cancellable.
public async Task<Data> FetchDataAsync(CancellationToken cancellationToken = default)
{
// Pass token to all async calls
var response = await httpClient.GetAsync(url, cancellationToken);
// Check for cancellation in loops
foreach (var item in items)
{
cancellationToken.ThrowIfCancellationRequested();
await ProcessItemAsync(item, cancellationToken);
}
return result;
}
// Usage
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
try
{
var data = await FetchDataAsync(cts.Token);
}
catch (OperationCanceledException)
{
// Handle timeout/cancellation
}
Cancellation patterns:
// Timeout
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
// Manual cancellation
var cts = new CancellationTokenSource();
cts.Cancel();
// Linked tokens (cancel if any source cancels)
var linked = CancellationTokenSource.CreateLinkedTokenSource(token1, token2);
// Register cleanup
cancellationToken.Register(() => CleanupResources());
Library code shouldn't capture the synchronization context.
// APPLICATION code: Keep context (UI updates, HttpContext)
public async Task UpdateUIAsync()
{
var data = await GetDataAsync(); // Default: captures context
label.Text = data; // Runs on UI thread
}
// LIBRARY code: Don't need context
public async Task<string> GetDataAsync()
{
var response = await httpClient.GetAsync(url)
.ConfigureAwait(false); // Don't capture context
return await response.Content.ReadAsStringAsync()
.ConfigureAwait(false);
}
Rule of thumb:
.ConfigureAwait(false)Don't await in loops when operations are independent.
Not this:
// SEQUENTIAL: Each awaits before next starts
var results = new List<Data>();
foreach (var id in ids)
{
var data = await FetchAsync(id); // One at a time
results.Add(data);
}
This:
// CONCURRENT: All start immediately
var tasks = ids.Select(id => FetchAsync(id));
var results = await Task.WhenAll(tasks);
Error handling with WhenAll:
var tasks = ids.Select(id => FetchAsync(id)).ToList();
try
{
var results = await Task.WhenAll(tasks);
}
catch
{
// Only first exception thrown
// Get all exceptions:
var exceptions = tasks
.Where(t => t.IsFaulted)
.Select(t => t.Exception);
}
When methods often complete synchronously, avoid Task allocation.
// Task: Always allocates
public async Task<int> GetValueAsync()
{
if (cache.TryGetValue(key, out var value))
return value; // Still allocates Task<int>
return await FetchFromDatabaseAsync();
}
// ValueTask: No allocation for sync path
public async ValueTask<int> GetValueAsync()
{
if (cache.TryGetValue(key, out var value))
return value; // No allocation!
return await FetchFromDatabaseAsync();
}
ValueTask rules:
ASP.NET is already async. Task.Run just moves work to another thread.
Not this:
// WRONG in ASP.NET: Pointless thread hop
public async Task<IActionResult> GetData()
{
var data = await Task.Run(() => ProcessData()); // Why?
return Ok(data);
}
This:
// If ProcessData is CPU-bound and slow, just call it
public IActionResult GetData()
{
var data = ProcessData(); // Direct call
return Ok(data);
}
// If it's I/O-bound, make it async
public async Task<IActionResult> GetData()
{
var data = await ProcessDataAsync();
return Ok(data);
}
When Task.Run is appropriate:
public class Service
{
private readonly AsyncLazy<Database> _db;
public Service()
{
_db = new AsyncLazy<Database>(() => InitializeDatabaseAsync());
}
public async Task<Data> GetDataAsync()
{
var db = await _db;
return await db.QueryAsync();
}
}
// AsyncLazy implementation
public class AsyncLazy<T> : Lazy<Task<T>>
{
public AsyncLazy(Func<Task<T>> factory)
: base(() => Task.Run(factory)) { }
public TaskAwaiter<T> GetAwaiter() => Value.GetAwaiter();
}
public class Connection : IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
await CloseAsync();
GC.SuppressFinalize(this);
}
}
// Usage with await using
await using var connection = new Connection();
await connection.SendAsync(data);
// DisposeAsync called automatically
public async IAsyncEnumerable<Item> GetItemsAsync(
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await foreach (var batch in GetBatchesAsync(cancellationToken))
{
foreach (var item in batch)
{
yield return item;
}
}
}
// Consuming
await foreach (var item in GetItemsAsync(cancellationToken))
{
await ProcessAsync(item);
}
private readonly SemaphoreSlim _semaphore = new(maxConcurrency: 10);
public async Task<Data> FetchWithThrottleAsync(string url)
{
await _semaphore.WaitAsync();
try
{
return await httpClient.GetAsync(url);
}
finally
{
_semaphore.Release();
}
}
// NEVER DO THIS
public Data GetData()
{
return GetDataAsync().Result; // Blocks, deadlock risk
return GetDataAsync().GetAwaiter().GetResult(); // Still blocks
Task.Run(() => GetDataAsync()).Result; // Thread pool exhaustion
}
// MISLEADING: Looks async, isn't
public Task<Data> GetDataAsync()
{
return Task.FromResult(ComputeDataSync()); // Blocks caller's thread
}
// If you must wrap sync code, be explicit
public Task<Data> GetDataAsync()
{
return Task.Run(() => ComputeDataSync()); // At least caller isn't blocked
}
// BAD: Exception disappears
_ = DoSomethingAsync();
// BETTER: Handle errors
_ = DoSomethingAsync().ContinueWith(t =>
{
if (t.IsFaulted)
logger.LogError(t.Exception);
});
// BEST: Use a proper fire-and-forget helper
BackgroundTask.Run(async () =>
{
await DoSomethingAsync();
});
Before committing async code, ask:
.Result, .Wait(), or .GetAwaiter().GetResult()?| Scenario | Apply Cleary |
|---|---|
| Any async/await code | Yes |
| Cancellation patterns | Yes |
| Concurrent operations | Yes |
| Deadlock debugging | Yes |
| Thread synchronization (locks) | Partially - async locks differ |
| Parallel CPU-bound work | No - use TPL/PLINQ |
"There is no thread." — Stephen Cleary (on understanding async)