Async Lambda
Metaprogramming library provides full support of dynamic generation of async lambda functions. This functionality is not supported by LINQ Expressions out-of-the-box.
There are three key elements required to generated async lambda:
- CodeGenerator.AsyncLambda method used to build async lambda function instead of
CodeGenerator.Lambda
method suitable for synchronous lambda functions only. - AsyncResultExpression used to return value from the asynchronous lambda function (known as async return). Usually, the developer don't need to use this type of expression directly.
- AwaitExpression is similar to await operator.
await operator can be used even in try-catch-finally statement and means that async lambda function works in the same way as async methods in C#.
Let's translate the following example of async method in C# programming language.
using System;
using System.Net.Http;
using System.Threading.Tasks;
private static async Task<long> GetPageSizeAsync(string url)
{
var client = new HttpClient();
var uri = new Uri(url);
var urlContents = await client.GetByteArrayAsync(uri);
return urlContents.LongLength;
}
Await()
extension method from ExpressionBuilder applicable to any object of type Expression is an equivalent of await operator and do all necessary magic. Note that Await()
is applicable inside of async lambda function. If await operator is used inside of synchronous async lambda function then compiled code will block the thread during execution of the expression used as an argument for this operator.
using DotNext.Linq.Expressions;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using static DotNext.Metaprogramming.CodeGenerator;
AsyncLambda<Func<string, Task<long>>>((fun, result) =>
{
var client = DeclareVariable("client", typeof(HttpClient).New()); //var client = new HttpClient();
var uri = DeclareVariable("uri", typeof(Uri).New(fun[0])); //var uri = new Uri(url);
var urlContents = DeclareVariable<byte[]>("urlContents");
Assign(urlContents, client.Call(nameof(HttpClient.GetByteArrayAsync), uri).Await()); //urlContents = await client.GetByteArrayAsync(uri);
Assign(result, urlContents);
});
await operator can be placed inside of any statement: switch, if-then-else, try-catch-finally etc.
AsyncLambda
factory method is overloaded by two versions of this method:
AsyncLambda((fun, result) => { })
introduces special variableresult
that can be used to assign result of the function. This approach is similar to Result variable in Pascal programming language.AsyncLambda(fun => { })
doesn't introduce special variable for the function result and control transfer to the caller is provided byCodeGenerator.Return
method.
fun
parameter is of type LambdaContext that provides access to the function parameters.
Limitations
Async lambda function has the following limitations:
- The return type of the delegate representing lambda function should have one of the following return types:
- All limitations inherited from LINQ Expression framework which are described here
void return type is not supported. Instead of void return type use Task or ValueTask.
However, await operator is applicable to any async method with return type which differs from supported task types without limitations.
Code Generation Principles
Metaprogramming library transforms the body of async lambda function into state machine. The algorithm of transformation is similar to Roslyn Compiler but not equal. You can start from this source code to learn how Roslyn transforms the code of async method.
The data type used to store local variables in the form of machine state is not compiler-generated because LINQ Expression doesn't have dynamic type generation functionality. Value tuples are used instead of dynamically generated type.
try-catch-finally block is transformed into code without structured exception handling instructions. The exception management is centralized by async state machine.
The exception raised by the async lambda function is stored inside of async state machine, not as separated field in the machine state value type generated by Roslyn Compiler.
Async State Machine class contains low-level methods allow to control execution of async lambda function. To be more precise, there are two async state machine classes:
- AsyncStateMachine<TState> is used by async lambda function without return value.
- AsyncStateMachine<TState, R> is used by async lambda function with return value.
Implementation of the state machine optimized for situations when asynchronous method completed synchronously. In this case, the state machine will not be boxed.
Caution
AsyncStateMachine class is internal type and is not available publicly.