Async Programming¶
Implementation status: ✅ Implemented (v0.2.x). -
async defis parsed and emits C#asyncmethods returningTaskorTask<T>. -awaitexpressions are implemented —awaitcan be used insideasync deffunctions to unwrapTask<T>results. -async foris implemented — maps toawait foreachoverIAsyncEnumerable<T>. -async withis implemented — supports both dunder protocol andIAsyncDisposable, including multiple context managers. -asyncio.gather()is implemented — maps toTask.WhenAll(). - Async generators (async defwithyield) emitIAsyncEnumerable<T>return type. -yield fromin async generators is implemented (Sharpy extension beyond Python). - Async constructors (async def __init__) are rejected at compile time (SPY0358). - Async comprehensions ([x async for x in ...],[await f(x) for x in ...]) are implemented insideasync deffunctions for list/set/dict comprehensions — see Async Comprehensions below. Async generator expressions ((x async for x in ...)) remain unsupported.See generators.md for synchronous and async generator documentation.
Async Functions¶
async def fetch_data(url: str) -> str:
await asyncio.sleep(1.0)
return f"Data from {url}"
async def main():
result = await fetch_data("https://example.com")
print(result)
Implementation: ✅ Implemented — async def is parsed and emits C# async Task<T> methods. await expressions unwrap Task<T> to T. Using await outside async def or on non-Task types produces compile errors (SPY0273, SPY0274).
Concurrent Execution¶
async def fetch_all(urls: list[str]) -> list[str]:
tasks = [fetch_data(url) for url in urls]
results = await asyncio.gather(*tasks)
return results
Implementation: ✅ Implemented — asyncio.gather(*tasks) maps to await Task.WhenAll(tasks).
Async Iteration¶
async def count_up(n: int) -> int:
for i in range(n):
yield i
async def process():
async for num in count_up(5):
print(f"Number: {num}")
Implementation: ✅ Implemented — async def with yield emits IAsyncEnumerable<T>. async for maps to await foreach. yield from in async generators auto-detects sync vs async iterables (Sharpy extension beyond Python).
Async Comprehensions¶
Sharpy supports async comprehensions inside async def functions. List, set, and dict comprehensions may use:
- An
async forclause to iterate an async iterable (an async generator or anyIAsyncEnumerable<T>). awaitin the element expression, in a dict key or value, and in aniffilter.
async def gen() -> int: # async generator — annotate its element type
for i in range(3):
yield i
async def scale(x: int) -> int:
return x * 10
async def keep(x: int) -> bool:
return x > 0
async def collect() -> list[int]:
# async for clause
items = [x async for x in gen()] # [0, 1, 2]
# await in the element, plus an awaited filter
scaled = [await scale(x) async for x in gen() if await keep(x)] # [10, 20]
# set and dict comprehensions work the same way
seen = {x async for x in gen()} # {0, 1, 2}
table = {x: await scale(x) async for x in gen()} # {0: 0, 1: 10, 2: 20}
return scaled
Rules and semantics:
async def-only. Async comprehensions (anasync forclause or anawaitanywhere in a comprehension) are only valid inside anasync deffunction — the same rule Python enforces.- Sequential execution. Faithful to Python, elements are produced one at a time in order; there is no implicit concurrency. When you want concurrency, use
asyncio.gather(see Concurrent Execution), which maps toTask.WhenAll(). - Annotate async generators. An async generator used in an
async forclause should declare its element type (e.g.async def gen() -> int:) so the comprehension can infer the result type. - Nesting. Async comprehensions may be nested: an async comprehension can appear in the element expression, a dict key/value, a for-clause iterator, or an
iffilter of another comprehension. Each inner comprehension is rebuilt on every outer iteration, with the outer loop variable in scope — e.g.[[await dbl(y) async for y in inner(x)] async for x in outer()].
Implementation: ✅ Implemented — each async comprehension lowers to a temporary collection populated by an await foreach (for an async for clause) or a foreach (when only await appears in the body), appending each element/entry in order; the temporary is the comprehension's result. Nested async comprehensions lower to nested loops, so the inner collection is recreated per outer iteration. Using an async for clause or await in a comprehension outside async def is a compile error.
Async generator expressions —
(x async for x in src)— are not supported, because Sharpy has no generator-expression construct at all (synchronous generator expressions are likewise unavailable). Use a list/set/dict comprehension or an explicitasync forloop instead.
Generator Return Types:
Functions using yield have special return type annotations:
| Pattern | Return Type | Implementation Status |
|---|---|---|
yield in function |
-> T (compiler infers IEnumerable<T>) |
✅ Implemented |
yield in async def |
-> T (compiler infers IAsyncEnumerable<T>) |
✅ Implemented |
yield from in function |
Same as yielded iterator | ✅ Implemented |
yield from in async def |
Auto-selects foreach or await foreach |
✅ Implemented (Sharpy extension) |
# ✅ Synchronous generator
def fibonacci(n: int) -> int:
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
# ✅ Async generator
async def async_count(n: int) -> int:
for i in range(n):
yield i
# ✅ yield from in async generator (Sharpy extension — not valid Python)
async def combined() -> int:
yield 1
yield from sync_items() # sync iterable → foreach
yield 2
See generators.md for complete synchronous generator documentation.
Async Context Managers¶
class AsyncResource:
async def __aenter__(self) -> AsyncResource:
print("entering")
return self
async def __aexit__(self):
print("exiting")
async def main():
async with AsyncResource() as resource:
print("using resource")
async with supports two protocols:
- Async dunder protocol: Classes with __aenter__/__aexit__ → try/finally with await AenterAsync()/await AexitAsync()
- IAsyncDisposable: .NET types → await using
Multiple context managers are supported in a single async with statement, just like synchronous with:
Each context manager is nested inside-out in the generated code (last item wraps the body, first item wraps everything), ensuring proper cleanup order.
See context_managers.md for full details on both sync and async context manager protocols.
Implementation: ✅ Implemented — async dunder protocol emits try/finally; IAsyncDisposable emits await using. Multiple context managers in a single async with are supported.