采用 C# 的 Lambda 函数处理程序
Lambda 函数处理程序是函数代码中处理事件的方法。当调用函数时,Lambda 运行处理程序方法。当该处理程序退出或返回一个响应时,它便可用于处理另一个事件。
将 Lambda 函数处理程序定义为某个类中的实例或静态方法。如需访问 Lambda 上下文对象,您可以定义类型 ILambdaContext 的方法参数。您可以使用此方法来访问与当前调用相关的信息,例如函数的名称、内存限制、剩余的执行时间和日志记录。
returnType
handler-name
(inputType
input, ILambdaContext context) { ... }
在该语法中,需要注意以下方面:
-
inputType
– 第一个处理程序参数为处理程序的输入。它可以是事件数据(由事件源发布)或您提供的自定义输入(如字符串或任意自定义数据对象)。 -
returnType
– 如果计划同步调用 Lambda 函数(使用RequestResponse
调用类型),则可以使用任何支持的数据类型返回函数输出。例如,如果使用 Lambda 函数作为移动应用程序后端,则同步调用该函数。您的输出数据类型将会序列化为 JSON。如果计划异步调用 Lambda 函数(使用
Event
调用类型),则returnType
应为void
。例如,将 Lambda 搭配 Amazon Simple Storage Service (Amazon S3) 或 Amazon Simple Notification Service (Amazon SNS) 之类的事件源使用时,这些事件源会使用Event
调用类型调用 Lambda 函数。 -
ILambdaContext context
– 处理程序签名中的第二个参数是可选的。它提供对上下文对象的访问权限,该对象包含有关函数和请求的信息。
处理流
默认情况下,Lambda 只支持将 System.IO.Stream
类型作为输入参数。
例如,考虑以下 C# 示例代码。
using System.IO; namespace Example { public class Hello { public Stream MyHandler(Stream stream) { //function logic } } }
在示例 C# 代码中,第一个处理程序参数为处理程序 (MyHandler) 的输入。它可以是事件数据(由 Amazon S3 之类的事件源发布)或您提供的自定义输入,如 Stream
(和此示例一样)或任意自定义数据对象。输出的类型为 Stream
。
处理标准数据类型
以下所有其他类型均需要您指定序列化器:
-
Primitive .NET 类型(例如字符串或 int)
-
集合和映射 - IList、IEnumerable、IList<T>、Array、IDictionary、IDictionary<TKey 和 TValue>
-
POCO 类型(无格式的旧 CLR 对象)
-
预定义的Amazon事件类型
-
对于异步调用,Lambda 将忽略返回类型。在这些情况下,可以将返回类型设置为 void。
-
如果您使用的是 .NET 异步编程,则返回类型可以是 Task 和 Task<T>,并且可使用
async
和await
关键字。有关更多信息,请参阅 将 C# 函数中的 Async 与 Lambda 搭配使用。
除非函数输入和输出参数的类型为 System.IO.Stream
,否则您需要对这些参数执行序列化。Lambda 提供了可在应用程序的程序集或方法级别应用的默认序列化器,或者您也可以通过实施由 Amazon.Lambda.Core
库提供的 ILambdaSerializer
接口定义自己的序列化器。有关更多信息,请参阅 使用 .zip 文件归档部署 C# Lambda 函数。
要向某个方法添加默认的串行器属性,请先添加对 Amazon.Lambda.Serialization.SystemTextJson
文件中 .csproj
的依赖关系。
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles> <AWSProjectType>Lambda</AWSProjectType> <!-- Makes the build directory similar to a publish directory and helps the Amazon .NET Lambda Mock Test Tool find project dependencies. --> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <!-- Generate ready to run images during publishing to improve cold start time. --> <PublishReadyToRun>true</PublishReadyToRun> </PropertyGroup> <ItemGroup> <PackageReference Include="Amazon.Lambda.Core" Version="2.1.0 " /> <PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.2.0" /> </ItemGroup> </Project>
以下示例说明了您可以分别针对自己选择的各种方法灵活地指定默认 System.Text.Json 序列化器:
public class ProductService { [LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] public Product DescribeProduct(DescribeProductRequest request) { return catalogService.DescribeProduct(request.Id); } [LambdaSerializer(typeof(MyJsonSerializer))] public Customer DescribeCustomer(DescribeCustomerRequest request) { return customerService.DescribeCustomer(request.Id); } }
用于 JSON 序列化的源生成
C# 9 提供了可在编译期间允许代码生成的源生成器。从 .NET 6 开始,本地 JSON 库 System.Text.Json
可以使用源生成器,以允许进行 JSON 解析而无需反映 API。这可以帮助提高冷启动性能。
使用源生成器
-
在您的项目中,定义从
System.Text.Json.Serialization.JsonSerializerContext
衍生的空部分类。 -
为源生成器必须为其生成序列化代码的每个 .NET 类型添加
JsonSerializable
属性。
例 利用原生成的 API 网关集成
using System.Collections.Generic; using System.Net; using System.Text.Json.Serialization; using Amazon.Lambda.Core; using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.Serialization.SystemTextJson; [assembly: LambdaSerializer(typeof(SourceGeneratorLambdaJsonSerializer<SourceGeneratorExa mple.HttpApiJsonSerializerContext>))] namespace SourceGeneratorExample; [JsonSerializable(typeof(APIGatewayHttpApiV2ProxyRequest))] [JsonSerializable(typeof(APIGatewayHttpApiV2ProxyResponse))] public partial class HttpApiJsonSerializerContext : JsonSerializerContext { } public class Functions { public APIGatewayProxyResponse Get(APIGatewayHttpApiV2ProxyRequest request, ILambdaContext context) { context.Logger.LogInformation("Get Request"); var response = new APIGatewayHttpApiV2ProxyResponse { StatusCode = (int)HttpStatusCode.OK, Body = "Hello AWS Serverless", Headers = new Dictionary<string, string> { { "Content-Type", "text/plain" } } }; return response; } }
当您调用函数时,Lambda 将使用源生成的 JSON 序列化代码来处理 Lambda 事件和响应的序列化。
处理程序签名
创建 Lambda 函数时,您必须提供一个处理程序字符串,告知 Lambda 在何处查找要调用的代码。在 C# 中,格式如下:
ASSEMBLY::TYPE::METHOD
,其中:
-
ASSEMBLY
是您的应用程序的 .NET 程序集文件的名称。使用 .NET Core CLI 构建应用程序时,如果未使用 .csproj 中的AssemblyName
属性来设置程序集名称,则ASSEMBLY
名称将为 .csproj 文件名称。有关更多信息,请参阅 .NET Core CLI。在这种情况下,假设 .csproj 文件为HelloWorldApp.csproj
。 -
TYPE
是包含Namespace
和ClassName
的处理程序类型的全名。在本例中为Example.Hello
。 -
METHOD
是函数处理程序的名称,在本例中为MyHandler
。
签名最终将是以下格式:Assembly::Namespace.ClassName::MethodName
考虑以下示例:
using System.IO; namespace Example { public class Hello { public Stream MyHandler(Stream stream) { //function logic } } }
处理程序字符串为:HelloWorldApp::Example.Hello::MyHandler
如果处理程序字符串指定的方法重载,则您必须提供 Lambda 应调用的方法的准确签名。如果该解决方法需要在多个(重载)签名中进行选择,则 Lambda 将会拒绝其他有效签名。
使用顶级语句
从 .NET 6 开始,您可以使用顶级语句编写函数。顶级语句删除了 .NET 项目所需的部分样本代码,从而减少了您编写代码的行数。例如,您可以使用顶级语句重写上一个示例:
using Amazon.Lambda.RuntimeSupport; var handler = (Stream stream) => { //function logic }; await LambdaBootstrapBuilder.Create(handler).Build().RunAsync();
使用顶级语句时,如果提供处理程序签名,则您只需包含 ASSEMBLY
名称。继续前述示例,处理程序字符串为 HelloWorldApp
。
通过将处理程序设置为程序集名称,Lambda 会将此程序集处理为可执行项,并在启动时执行它。您必须将 NuGet 程序包 Amazon.Lambda.RuntimeSupport
添加到项目,以便在启动时运行的可执行项启动 Lambda 运行时客户端。
序列化 Lambda 函数
对于任何使用 Stream
对象以外的输入或输出类型的 Lambda 函数,您必须向应用程序中添加一个序列化库。您可以通过下列方式来执行此操作:
-
使用
Amazon.Lambda.Serialization.SystemTextJson
NuGet 程序包。该库使用本地 .NET Core JSON 序列化器来处理序列化。此包通过Amazon.Lambda.Serialization.Json
提供性能改进,但请注意 Microsoft 文档中所述的限制。此库适用于 .NET Core 3.1 及更高版本的运行时。 -
使用
Amazon.Lambda.Serialization.Json
NuGet 程序包。该库使用 JSON.NET 来处理序列化。 -
通过实施
ILambdaSerializer
接口(作为Amazon.Lambda.Core
库的一部分提供)创建您自己的序列化库。该接口定义了两种方法:-
T Deserialize<T>(Stream requestStream);
通过实施此方法,您可以将请求负载从
Invoke
API 反序列化至传递到 Lambda 函数处理程序的对象中。 -
T Serialize<T>(T response, Stream responseStream);
.通过实施此方法,您可以将从 Lambda 函数处理程序中返回的结果序列化到
Invoke
API 操作返回的响应负载中。
-
要使用序列化器,您必须向 MyProject.csproj
文件添加依赖项。
... <ItemGroup> <PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" Version="2.1.0" /> <!-- or --> <PackageReference Include="Amazon.Lambda.Serialization.Json" Version="2.0.0" /> </ItemGroup>
接下来,必须定义序列化器。以下示例在 AssemblyInfo.cs 文件中定义了
Amazon.Lambda.Serialization.SystemTextJson
序列化器。
[assembly: LambdaSerializer(typeof(DefaultLambdaJsonSerializer))]
以下示例在 AssemblyInfo.cs 文件中定义了 Amazon.Lambda.Serialization.Json
序列化器。
[assembly: LambdaSerializer(typeof(JsonSerializer))]
您可以在方法级别定义一个自定义序列化属性,以覆盖在程序集级别指定的默认序列化器。
public class ProductService{ [LambdaSerializer(typeof(JsonSerializer))] public Product DescribeProduct(DescribeProductRequest request) { return catalogService.DescribeProduct(request.Id); } [LambdaSerializer(typeof(MyJsonSerializer))] public Customer DescribeCustomer(DescribeCustomerRequest request) { return customerService.DescribeCustomer(request.Id); } }
Lambda 函数处理程序限制
请注意,处理程序签名存在一些限制。
-
它不能是
unsafe
并且不得在处理程序签名中使用指针类型,但您可以在处理程序方法及其依赖项中使用unsafe
上下文。有关更多信息,请参阅 Microsoft 文档网站上的不安全(C# 参考) -
处理程序签名不得使用
params
关键字传递可变数量的参数,也不得将用于支持可变数量参数的ArgIterator
用作输入或返回参数。 -
处理程序不能是泛型方法,例如,IList<T> Sort<T>(IList<T> input)。
-
不支持使用签名
async void
的 Async 处理程序。
将 C# 函数中的 Async 与 Lambda 搭配使用
如果您知道自己的 Lambda 函数需要长时间的运行过程,例如上传大型文件到 Amazon S3 或者从 Amazon DynamoDB 中读取大量记录,则您可以利用 async/await 模式。当您使用此签名时,Lambda 会同步调用函数并等待该函数返回响应或执行超时。
public async Task<Response> ProcessS3ImageResizeAsync(SimpleS3Event input) { var response = await client.DoAsyncWork(input); return response; }
如果使用此模式,请考虑以下事项:
-
Lambda 不支持
async void
方法。 -
如果您创建了一个 async Lambda 函数,但未实施
await
运算符,.NET 将发出一个编译器,提醒您注意意外行为。例如,有些 async 操作会运行,而有些不会。或者,有些 async 操作不会在函数调用完成时结束。public async Task ProcessS3ImageResizeAsync(SimpleS3Event event) // Compiler warning { client.DoAsyncWork(input); }
-
您的 Lambda 函数可包含多个可并行调用的 async 调用。您可使用
Task.WhenAll
和Task.WhenAny
方法来处理多项任务。要使用Task.WhenAll
方法,您需要将一个操作列表作为阵列传递至该方法。请注意,在以下示例中,如果您忘记在该阵列中包含任何操作,则该调用可能会在操作结束之前返回。public async Task DoesNotWaitForAllTasks1() { // In Lambda, Console.WriteLine goes to CloudWatch Logs. var task1 = Task.Run(() => Console.WriteLine("Test1")); var task2 = Task.Run(() => Console.WriteLine("Test2")); var task3 = Task.Run(() => Console.WriteLine("Test3")); // Lambda may return before printing "Test2" since we never wait on task2. await Task.WhenAll(task1, task3); }
要使用
Task.WhenAny
方法,您同样需要将一个操作列表作为阵列传递至该方法。该调用将在第一个操作结束时返回,即使其他操作仍在运行也是如此。public async Task DoesNotWaitForAllTasks2() { // In Lambda, Console.WriteLine goes to CloudWatch Logs. var task1 = Task.Run(() => Console.WriteLine("Test1")); var task2 = Task.Run(() => Console.WriteLine("Test2")); var task3 = Task.Run(() => Console.WriteLine("Test3")); // Lambda may return before printing all tests since we're waiting for only one to finish. await Task.WhenAny(task1, task2, task3); }