Async functions
Async functions let a program wait for asynchronous work without blocking the current thread. They are commonly used for operations such as network requests, file access, database calls, and timers.
Declare an async function with the async modifier and use await to wait for
an asynchronous operation:
async func DownloadPage() {
use client = HttpClient()
let response = await client.GetAsync("https://example.com")
let content = await response.Content.ReadAsStringAsync()
Console.WriteLine(content)
}
While an awaited operation is incomplete, execution of the function may be suspended. It resumes when the operation completes.
The async modifier can be used with top-level functions, methods, and local
functions.
Returning values
An async function that doesn't return a value should declare
System.Threading.Tasks.Task as its return type:
async func Delay() -> Task {
await Task.Delay(1000)
}
Use Task<T> when the function produces a value:
async func GetMessage() -> Task<string> {
await Task.Delay(100)
return "Hello"
}
As with other Raven functions, the final expression of a block can provide its result:
async func GetMessage() -> Task<string> {
await Task.Delay(100)
"Hello"
}
Async functions can also use expression bodies:
async func GetMessage() -> Task<string> =>
await GetMessageFromServer()
Raven does not infer Task<T> from the body of an async function. If an async
function returns a value, its task return type must be declared explicitly.
async func GetMessage() -> Task<string> {
"Hello"
}
For Task<T>, returned values must be convertible to T.
A Task function does not return a value. Falling off the end of the function
is equivalent to return:
async func Save() -> Task {
await SaveChanges()
}
An existing task is not returned directly from an async Task function.
Instead, await it:
async func Save() -> Task {
await SaveChanges()
}
Await expressions
Use await to suspend an async function until an asynchronous operation
completes:
async func LoadUser(id: int) -> Task<User> {
let response = await client.GetAsync($"/users/{id}")
let user = await response.Content.ReadFromJsonAsync<User>()
user
}
The type of an await expression is the result produced by the awaited
operation. For example, awaiting Task<string> produces a string:
let text: string = await GetTextAsync()
Awaiting a Task that does not produce a value results in unit:
await Task.Delay(1000)
await can only be used in an async context.
Top-level await
Top-level code can also use await:
import System.Net.Http.*
use client = HttpClient()
let response = await client.GetAsync("https://example.com")
let content = await response.Content.ReadAsStringAsync()
Console.WriteLine(content)
No explicit async entry-point function is required. Raven creates the necessary program entry point and waits for the top-level asynchronous code to complete.
Awaiting .NET types
Raven supports the standard .NET awaitable pattern, so await is not limited
to Task and Task<T>.
An awaitable value must provide a GetAwaiter() method. Its awaiter must provide:
- an
IsCompleted: boolproperty - a parameterless
GetResult()method
Conceptually, an await expression:
let result = await operation
obtains the operation's awaiter. If the operation has already completed,
GetResult() can be evaluated immediately. Otherwise, the async function is
suspended and resumed when the operation completes.
The result type of await is the return type of GetResult(). If GetResult()
does not return a value, the expression has type unit.
This allows Raven to interoperate with .NET APIs that implement the awaitable
pattern without requiring their types to derive from or convert to Task.
Async declarations
Explicit return types on async functions must use one of the supported task shapes:
Task
Task<T>
ValueTask
ValueTask<T>
Other return types are rejected.
Async declarations may use either block bodies or expression bodies. Within an
async Task<T> function, each explicit return expression must be convertible to
T. Within an async Task function, return cannot carry an expression.
The same rules apply to async methods, top-level functions, and local functions.
Property and indexer accessors may also be async. Async getters must expose a task-shaped return type, while async setters may await asynchronous work before storing a value.
Unawaited calls
An ignored call returning Task, Task<T>, ValueTask, or ValueTask<T>
reports the default analyzer warning RAV9038, in both async and ordinary
functions. Await the operation to observe its completion and exceptions:
await Save()
Returning or storing the task handles its result without requiring an immediate
await. Use _ = Save() to mark an intentional discard. This warning is
independent of the returned-value handling mode and the disposable-value
analyzer. Configure dotnet_diagnostic.RAV9038.severity to change its severity.
Exceptions
Exceptions raised during asynchronous execution are propagated through the
usual .NET async mechanism. Awaiting a failed operation rethrows its exception
at the await expression.
Exceptions that escape before asynchronous execution is suspended propagate directly to the caller.
Detailed rules and diagnostics
awaitoutside an async context reportsRAV2700.- Awaiting a value that does not follow the awaitable pattern reports
RAV2701. An awaiter withoutIsCompleted: boolreportsRAV2702, and one without a parameterlessGetResult()reportsRAV2703. - An unsupported async return type reports
RAV2704. - Returning a value from an async function whose return type is
TaskorValueTaskreportsRAV2705. - An async body with no
awaitreports warningRAV2706because it runs synchronously.