使用 Azure WebJobs SDK 进行事件驱动的后台处理

本文介绍如何使用 Azure WebJobs SDK。 若要快速开始使用 WebJobs,请参阅 Azure WebJobs SDK 入门

WebJobs SDK 版本

WebJobs SDK 版本 3.x 与版本 2.x 之间的主要差别如下

  • 版本 3.x 中添加了对 .NET Core 的支持。
  • 在版本 3x 中,安装 WebJobs SDK 所需的存储绑定扩展。 在版本 2.x 中,存储绑定包含在 SDK 中。
  • 适用于 .NET Core (3.x) 项目的 Visual Studio 2019 工具与适用于 .NET Framework (2.x) 项目的工具不同 。 有关详细信息,请参阅 使用 Visual Studio 开发和部署 WebJobs

本文中的一些示例同时适用于 WebJobs 版本 3.x 和 WebJobs 版本 2.x

Azure Functions 基于 WebJobs SDK 构建:

  • Azure Functions 版本 2.x 以 WebJobs SDK 版本 3.x 为基础 。
  • Azure Functions 版本 1.x 以 WebJobs SDK 版本 2.x 为基础 。

Azure Functions 和 WebJobs SDK 的源代码存储库使用 WebJobs SDK 编号。 本文的多个部分提供了 Azure Functions 文档的链接。

WebJobs 主机

WebJobs 主机是函数的运行时容器。 主机侦听触发器并调用函数。 在版本 3.x 中,主机是 IHost 的实现。 在版本 2.x 中,使用的是 JobHost 对象。 在代码中创建主机实例,并编写代码来自定义其行为。

此体系结构更改反映了直接使用 WebJobs SDK 与通过 Azure Functions 间接使用 WebJobs 的主要差别。 在 Azure Functions 中,服务将控制主机。 你无法通过编写代码来自定义主机。 在 Azure Functions 中,可以通过 host.json 文件中的设置来自定义主机行为。 这些设置是字符串而不是代码,使用这些字符串会限制可执行的自定义类型。

主机连接

在本地运行时,WebJobs SDK 会在 local.settings.json 文件中查找 Azure 存储和 Azure 服务总线连接;在 Azure 中运行时,它会在 WebJob 的环境中查找这些连接。 默认情况下,WebJobs SDK 需要一个名称为 AzureWebJobsStorage 的存储连接。

当连接名称解析为单个精确值时,运行时会将该值标识为连接字符串,该字符串通常包括机密。 连接字符串的详细信息取决于连接的服务。 但是,连接名称还可以引用多个配置项的集合。 此方法可用于配置基于标识的连接。 可以使用以双下划线__结尾的共享前缀()将环境变量视为集合。 然后,可以通过将连接名称设置为此前缀来引用该组。

例如,Azure Blob 存储触发器定义的 connection 属性可能是 Storage1。 只要名为 Storage1 的环境变量没有配置单个字符串值,就可以使用名为 Storage1__blobServiceUri 的环境变量来通知连接的 blobServiceUri 属性。 每个服务的连接属性各不相同。 请参阅相关文档,了解使用连接的组件。

基于标识的连接

若要在 WebJobs SDK 中使用基于标识的连接,请确保在项目中使用最新版本的 WebJobs 包。 另外,应确保引用了 Microsoft.Azure.WebJobs.Host.Storage

以下示例显示了做出这些更新后项目文件的外观:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net48</TargetFramework>
    <IsWebJobProject>true</IsWebJobProject>
    <WebJobName>$(AssemblyName)</WebJobName>
    <WebJobType>Continuous</WebJobType>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.WebJobs" Version="3.0.42" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Storage.Queues" Version="5.3.1" />
    <PackageReference Include="Microsoft.Azure.WebJobs.Host.Storage" Version="5.0.1" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.1.1" />
  </ItemGroup>

  <ItemGroup>
    <None Update="appsettings.json">
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

在 HostBuilder 实例中设置 WebJobs 时,请确保调用 AddAzureStorageCoreServices 。 此调用允许 AzureWebJobsStorage 及其他存储触发器和绑定使用标识。

下面是一个示例:

    builder.ConfigureWebJobs(b =>
    {
        b.AddAzureStorageCoreServices();
        // Other configurations...
    });

然后,可以通过在 Azure 应用服务中托管代码时设置环境变量(或应用程序设置)来配置 AzureWebJobsStorage 连接:

环境变量 说明 示例值
AzureWebJobsStorage__blobServiceUri 存储帐户的 blob 服务的数据平面 URI。 它使用 HTTPS 方案。 <https://storage_account_name.blob.core.chinacloudapi.cn>
AzureWebJobsStorage__queueServiceUri 存储帐户的队列服务的数据平面 URI。 它使用 HTTPS 方案。 <https://storage_account_name.queue.core.chinacloudapi.cn>

如果以环境变量以外的任何方式提供配置(如配置文件中 appsettings.json ),则必须为连接及其属性提供结构化配置:

{
    "AzureWebJobsStorage": {
        "blobServiceUri": "https://<storage_account_name>.blob.core.chinacloudapi.cn",
        "queueServiceUri": "https://<storage_account_name>.queue.core.chinacloudapi.cn"
    }
}

如果你不打算使用 Blob 触发器,则可以省略 queueServiceUri 属性。

当代码在本地运行时,默认会使用开发人员标识,如 DefaultAzureCredential 介绍内容中所述。

在应用服务中托管代码时,前面的示例中的配置默认为 资源系统分配的托管标识 。 若要改用分配给应用的 用户分配标识 ,必须添加连接的属性以指定要使用的标识。 credential 属性(AzureWebJobsStorage__credential 为环境变量)应设置为字符串 managedidentity。 属性 clientIdAzureWebJobsStorage__clientId 作为环境变量)应设置为要使用的用户分配的托管标识的客户端 ID。

作为结构化配置,完整的对象如以下示例所示:

{
    "AzureWebJobsStorage": {
        "blobServiceUri": "https://<storage_account_name>.blob.core.chinacloudapi.cn",
        "queueServiceUri": "https://<storage_account_name>.queue.core.chinacloudapi.cn",
        "credential": "managedidentity",
        "clientId": "<user-assigned-identity-client-id>"
    }
}

用于 AzureWebJobsStorage 的标识应具有授予其存储 Blob 数据所有者存储队列数据参与者存储帐户参与者角色的角色分配。 如果不打算使用 Blob 触发器,可以省略 存储队列数据参与者存储帐户参与者 角色。

下表显示了在正常操作中使用触发器时建议使用的内置角色。 应用程序可能需要更多权限,具体取决于你编写的代码。

捆绑 内置角色示例
Blob 触发器 存储 Blob 数据所有者存储队列数据参与者
另请参阅上述 AzureWebJobsStorage 要求。
Blob(输入) 存储 Blob 数据读者
Blob(输出) 存储 Blob 数据所有者
队列触发器 存储队列数据读取者存储队列数据消息处理者
队列(输出) 存储队列数据参与者存储队列数据消息发送方
服务总线触发器1 Azure 服务总线数据接收方Azure 服务总线数据所有者
服务总线(输出) Azure 服务总线数据发送方

1 若要从服务总线主题触发,角色分配必须具有服务总线订阅资源的有效范围。 如果仅包含主题,将发生错误。 某些客户端(例如 Azure 门户)不会将服务总线订阅资源公开为角色分配的范围。 在这些场景中,可以改用 Azure CLI。 有关详细信息,请参阅 Azure 服务总线的 Azure 内置角色

版本 2 中的连接字符串。x

版本 2。x 的 SDK 不需要连接字符串的特定名称。 在版本 2.x 中,可为这些连接字符串使用你自己的名称,并将其存储在其他位置。 可以使用 JobHostConfiguration 在代码中设置名称,如以下示例所示:

static void Main(string[] args)
{
    var _storageConn = ConfigurationManager
        .ConnectionStrings["MyStorageConnection"].ConnectionString;

    //// Dashboard logging is deprecated; use Application Insights.
    //var _dashboardConn = ConfigurationManager
    //    .ConnectionStrings["MyDashboardConnection"].ConnectionString;

    JobHostConfiguration config = new JobHostConfiguration();
    config.StorageConnectionString = _storageConn;
    //config.DashboardConnectionString = _dashboardConn;
    JobHost host = new JobHost(config);
    host.RunAndBlock();
}

注意

由于版本 3.x 使用默认的 .NET Core 配置 API,因此不存在用于更改连接字符串名称的 API。 有关详细信息,请参阅 使用 Visual Studio 开发和部署 WebJobs

主机开发设置

可在开发模式下运行主机,提高本地开发效率。 下面是在开发模式下运行时会自动更改的一些设置:

属性 开发设置
Tracing.ConsoleLevel TraceLevel.Verbose:最大化日志输出。
Queues.MaxPollingInterval 一个低值,用于确保队列方法立即触发。
Singleton.ListenerLockPeriod 设置为 15 秒有助于快速完成迭代开发。

启用开发模式的过程取决于 SDK 版本。

版本 3.x

在版本 3 中。x,使用标准 ASP.NET 核心 API 更改主机环境。 对 UseEnvironment 实例调用 HostBuilder 方法。 传递名为 development 的字符串,如以下示例中所示:

static async Task Main()
{
    var builder = new HostBuilder();
    builder.UseEnvironment("development");
    builder.ConfigureWebJobs(b =>
            {
                b.AddAzureStorageCoreServices();
            });
    var host = builder.Build();
    using (host)
    {
        await host.RunAsync();
    }
}

版本 2.x

JobHostConfiguration 类具有 UseDevelopmentSettings 方法,该方法支持开发模式。 以下示例演示如何使用开发设置。 要使 config.IsDevelopment 在本地运行时返回 true,请设置名为 AzureWebJobsEnv、值为 Development 的本地环境变量。

static void Main()
{
    config = new JobHostConfiguration();

    if (config.IsDevelopment)
    {
        config.UseDevelopmentSettings();
    }

    var host = new JobHost(config);
    host.RunAndBlock();
}

管理并发连接数(版本 2.x)

在版本 3.x 中,连接限制默认为无限次连接。 如果出于某种原因需要更改此限制,则可以使用 MaxConnectionsPerServer 类的 WinHttpHandler 属性。

在版本 2 中。x,可以使用该 ServicePointManager.DefaultConnectionLimit 属性控制与主机的并发连接数。 在 2.x 中,应在启动 WebJobs 主机之前,在默认值 的基础上增大此值2

使用 HttpClient 从某个函数发出的所有传出 HTTP 请求都会流经 ServicePointManager。 达到 DefaultConnectionLimit 中设置的值后,ServicePointManager 会开始将请求排队,然后再发送请求。 假设 DefaultConnectionLimit 设置为 2,并且代码发出了 1,000 个 HTTP 请求。 最初,只允许 2 个请求传入 OS。 其他 998 个请求将会排队,直到有可用的空间。 HttpClient 可能会超时,因为它似乎已发出请求,但是,OS 从未将此请求发送到目标服务器。 可能会出现看似不合理的行为:本地 HttpClient 花费了 10 秒来完成请求,但服务在 200 毫秒内就返回了每个请求。

ASP.NET 应用程序的默认值是 Int32.MaxValue,这可能非常适合在“基本”或更高级别应用服务计划中运行的 WebJobs。 WebJobs 通常需要 Always On 设置,并且仅受基本和更高应用服务计划支持。

如果 WebJob 在免费或共享应用服务计划中运行,则应用程序受应用服务沙盒的限制,该沙盒当前 连接限制为 600。 如果在 ServicePointManager 中指定无限制的连接数,则很有可能会达到沙盒连接阈值,并且站点将会关闭。 在这种情况下,将 DefaultConnectionLimit 设置为较低的数值,例如 200,可以防止这种情况发生,同时仍然允许足够的吞吐量。

必须在发出任何 HTTP 请求之前配置该设置。 出于此原因,WebJobs 主机不应自动尝试调整该设置。 可能会在主机启动之前出现 HTTP 请求,这可能会导致意外的行为。 最佳的做法是在初始化 Main 之前,立即在 JobHost 方法中设置值,如以下示例所示:

static void Main(string[] args)
{
    // Set this immediately so that it's used by all requests.
    ServicePointManager.DefaultConnectionLimit = Int32.MaxValue;

    var host = new JobHost();
    host.RunAndBlock();
}

触发器

WebJobs SDK 支持 Azure Functions 使用的同一组触发器和绑定。 在 WebJobs SDK 中,触发器特定于函数,与 WebJob 部署类型无关。 具有通过 SDK 创建的事件触发函数的 WebJobs 始终应作为已启用 Always On 的连续 WebJob 进行发布

函数必须是公共方法,并且必须包含一个触发器属性或 NoAutomaticTrigger 属性。

自动触发器

自动触发器调用函数来响应事件。 请考虑此示例,该函数是由添加到 Azure 队列存储的消息触发的。 该函数通过从 Blob 存储读取 Blob 来做出响应:

public static void Run(
    [QueueTrigger("myqueue-items")] string myQueueItem,
    [Blob("samples-workitems/{queueTrigger}", FileAccess.Read)] Stream myBlob,
    ILogger log)
{
    log.LogInformation($"BlobInput processed blob\n Name:{myQueueItem} \n Size: {myBlob.Length} bytes");
}

QueueTrigger 特性告诉运行时在 myqueue-items 中出现队列消息时调用函数。 Blob 属性告诉运行时使用队列消息读取 sample-workitems 容器中的 Blob。 samples-workitems 容器中 Blob 项的名称将直接从队列触发器以绑定表达式 ({queueTrigger}) 的形式获得。

注意

Web 应用可在进入非活动状态 20 分钟后超时,只有向实际 Web 应用发出请求才会重置计时器。 在 Azure 门户中查看应用的配置或向高级工具站点 (https://<app_name>.scm.chinacloudsites.cn) 发出请求不会重置计时器。 如果将托管作业的 Web 应用设置为持续运行、按计划运行或使用事件驱动的触发器,请在 Web 应用的 Azure“配置”页上启用“始终可用”设置 。 “始终可用”设置有助于确保这些类型的 Web 作业可靠运行。 此功能仅在基本、标准和高级定价层中提供。

手动触发器

若要手动触发某个函数,请使用 NoAutomaticTrigger 属性:

[NoAutomaticTrigger]
public static void CreateQueueMessage(
ILogger logger,
string value,
[Queue("outputqueue")] out string message)
{
    message = value;
    logger.LogInformation("Creating queue message: ", message);
}

手动触发函数的过程取决于 SDK 版本。

版本 3.x

static async Task Main(string[] args)
{
    var builder = new HostBuilder();
    builder.ConfigureWebJobs(b =>
    {
        b.AddAzureStorageCoreServices();
        b.AddAzureStorage();
    });
    var host = builder.Build();
    using (host)
    {
        var jobHost = host.Services.GetService(typeof(IJobHost)) as JobHost;
        var inputs = new Dictionary<string, object>
        {
            { "value", "Hello world!" }
        };

        await host.StartAsync();
        await jobHost.CallAsync("CreateQueueMessage", inputs);
        await host.StopAsync();
    }
}

版本 2.x

static void Main(string[] args)
{
    JobHost host = new JobHost();
    host.Call(typeof(Program).GetMethod("CreateQueueMessage"), new { value = "Hello world!" });
}

输入和输出绑定

通过输入绑定能够以声明方式将 Azure 或第三方服务中的数据提供给代码使用。 输出绑定提供更新数据的方式。 入门文章中演示了输入和输出绑定的示例。

通过将属性应用于方法返回值,可以对输出绑定使用方法返回值。

绑定类型

安装和管理绑定类型的过程取决于使用的是 SDK 版本 3.x 还是版本 2.x。 可以在特定绑定类型的 Azure Functions 参考文章的“包”部分找到要为该绑定类型安装的包。 有一种例外情况:文件触发器和绑定(对于本地文件系统)不受 Azure Functions 的支持。

版本 3.x

在版本 3.x 中,存储绑定包含在 Microsoft.Azure.WebJobs.Extensions.Storage 包中。 在 AddAzureStorage 方法中调用 ConfigureWebJobs 扩展方法:

static async Task Main()
{
    var builder = new HostBuilder();
    builder.ConfigureWebJobs(b =>
            {
                b.AddAzureStorageCoreServices();
                b.AddAzureStorage();
            });
    var host = builder.Build();
    using (host)
    {
        await host.RunAsync();
    }
}

若要使用其他触发器和绑定类型,请安装包含这些类型的 NuGet 包,并调用在扩展中实现的 Add<binding> 扩展方法。 例如,如果你要使用 Azure Cosmos DB 绑定,请安装 Microsoft.Azure.WebJobs.Extensions.CosmosDB 并调用 AddCosmosDB

static async Task Main()
{
    var builder = new HostBuilder();
    builder.ConfigureWebJobs(b =>
            {
                b.AddAzureStorageCoreServices();
                b.AddCosmosDB();
            });
    var host = builder.Build();
    using (host)
    {
        await host.RunAsync();
    }
}

若要使用属于核心服务的 Timer 触发器或 Files 绑定,请调用 AddTimersAddFiles 扩展方法。

版本 2.x

以下触发器和绑定类型包含在版本 2.xMicrosoft.Azure.WebJobs 包中:

  • Blob 存储
  • 队列存储
  • 表存储

若要使用其他触发器和绑定类型,请安装包含这些类型的 NuGet 包,并对 Use<binding> 对象调用 JobHostConfiguration 方法。 例如,如果你要使用 Timer 触发器,请安装 Microsoft.Azure.WebJobs.Extensions 并在 UseTimers 方法中调用 Main

static void Main()
{
    config = new JobHostConfiguration();
    config.UseTimers();
    var host = new JobHost(config);
    host.RunAndBlock();
}

要使用 Files 绑定,请安装 Microsoft.Azure.WebJobs.Extensions 并调用 UseFiles

执行上下文

在 WebJobs 中,可以绑定到 ExecutionContext 实例。 使用此绑定,可以访问在函数签名中作为参数的 ExecutionContext。 例如,以下代码使用上下文对象访问调用 ID,使用该 ID 可以关联给定函数调用生成的所有日志。

public class Functions
{
    public static void ProcessQueueMessage([QueueTrigger("queue")] string message,
        ExecutionContext executionContext,
        ILogger logger)
    {
        logger.LogInformation($"{message}\n{executionContext.InvocationId}");
    }
}

绑定到 ExecutionContext 的过程取决于你的 SDK 版本。

版本 3.x

AddExecutionContextBinding 方法中调用 ConfigureWebJobs 扩展方法:

static async Task Main()
{
    var builder = new HostBuilder();
    builder.ConfigureWebJobs(b =>
            {
                b.AddAzureStorageCoreServices();
                b.AddExecutionContextBinding();
            });
    var host = builder.Build();
    using (host)
    {
        await host.RunAsync();
    }
}

版本 2.x

前面所述的 Microsoft.Azure.WebJobs.Extensions 包还提供了一个可以通过调用 UseCore 方法注册的特殊绑定类型。 可以使用绑定在函数签名中定义 ExecutionContext 参数:

class Program
{
    static void Main()
    {
        config = new JobHostConfiguration();
        config.UseCore();
        var host = new JobHost(config);
        host.RunAndBlock();
    }
}

绑定配置

可以配置某些触发器和绑定的行为。 配置过程取决于 SDK 版本。

  • 版本 3.xAdd<Binding> 中调用 ConfigureWebJobs 方法时设置配置。
  • 版本 2.x 通过在传入 JobHost 的配置对象中设置属性来设置配置。

这些特定于绑定的设置等效于 Azure Functions 中的 host.json 项目文件中 的设置。

可配置以下绑定:

Azure Cosmos DB 触发器配置(版本 3.x)

此示例演示如何配置 Azure Cosmos DB 触发器:

static async Task Main()
{
    var builder = new HostBuilder();
    builder.ConfigureWebJobs(b =>
    {
        b.AddAzureStorageCoreServices();
        b.AddCosmosDB(a =>
        {
            a.ConnectionMode = ConnectionMode.Gateway;
            a.Protocol = Protocol.Https;
            a.LeaseOptions.LeasePrefix = "prefix1";

        });
    });
    var host = builder.Build();
    using (host)
    {
        await host.RunAsync();
    }
}

有关详细信息,请参阅 Azure Cosmos DB 绑定

Azure 事件中心触发器配置(版本 3.x

此示例演示如何配置事件中心触发器:

static async Task Main()
{
    var builder = new HostBuilder();
    builder.ConfigureWebJobs(b =>
    {
        b.AddAzureStorageCoreServices();
        b.AddEventHubs(a =>
        {
            a.BatchCheckpointFrequency = 5;
            a.EventProcessorOptions.MaxBatchSize = 256;
            a.EventProcessorOptions.PrefetchCount = 512;
        });
    });
    var host = builder.Build();
    using (host)
    {
        await host.RunAsync();
    }
}

有关详细信息,请参阅 事件中心绑定

队列存储触发器配置

以下示例演示如何配置队列存储触发器。

版本 3.x
static async Task Main()
{
    var builder = new HostBuilder();
    builder.ConfigureWebJobs(b =>
    {
        b.AddAzureStorageCoreServices();
        b.AddAzureStorage(a => {
            a.BatchSize = 8;
            a.NewBatchThreshold = 4;
            a.MaxDequeueCount = 4;
            a.MaxPollingInterval = TimeSpan.FromSeconds(15);
        });
    });
    var host = builder.Build();
    using (host)
    {
        await host.RunAsync();
    }
}

有关详细信息,请参阅 队列存储绑定

版本 2.x
static void Main(string[] args)
{
    JobHostConfiguration config = new JobHostConfiguration();
    config.Queues.BatchSize = 8;
    config.Queues.NewBatchThreshold = 4;
    config.Queues.MaxDequeueCount = 4;
    config.Queues.MaxPollingInterval = TimeSpan.FromSeconds(15);
    JobHost host = new JobHost(config);
    host.RunAndBlock();
}

有关详细信息,请参阅 host.json v1.x 参考

SendGrid 绑定配置(版本 3.x

此示例演示如何配置 SendGrid 输出绑定:

static async Task Main()
{
    var builder = new HostBuilder();
    builder.ConfigureWebJobs(b =>
    {
        b.AddAzureStorageCoreServices();
        b.AddSendGrid(a =>
        {
            a.FromAddress.Email = "samples@functions.com";
            a.FromAddress.Name = "Azure Functions";
        });
    });
    var host = builder.Build();
    using (host)
    {
        await host.RunAsync();
    }
}

有关详细信息,请参阅 SendGrid 绑定

服务总线触发器配置(版本 3.x

此示例演示如何配置服务总线触发器:

static async Task Main()
{
    var builder = new HostBuilder();
    builder.ConfigureWebJobs(b =>
    {
        b.AddAzureStorageCoreServices();
        b.AddServiceBus(sbOptions =>
        {
            sbOptions.MessageHandlerOptions.AutoComplete = true;
            sbOptions.MessageHandlerOptions.MaxConcurrentCalls = 16;
        });
    });
    var host = builder.Build();
    using (host)
    {
        await host.RunAsync();
    }
}

有关详细信息,请参阅 服务总线绑定

其他绑定的配置

某些触发器和绑定类型定义其自身的自定义配置类型。 例如,可以使用文件触发器来指定要监视的根路径,如以下示例所示。

版本 3.x
static async Task Main()
{
    var builder = new HostBuilder();
    builder.ConfigureWebJobs(b =>
    {
        b.AddAzureStorageCoreServices();
        b.AddFiles(a => a.RootPath = @"c:\data\import");
    });
    var host = builder.Build();
    using (host)
    {
        await host.RunAsync();
    }
}
版本 2.x
static void Main()
{
    config = new JobHostConfiguration();
    var filesConfig = new FilesConfiguration
    {
        RootPath = @"c:\data\import"
    };
    config.UseFiles(filesConfig);
    var host = new JobHost(config);
    host.RunAndBlock();
}

绑定表达式

在特性构造函数参数中,可以使用解析为来自各种源的值的表达式。 例如,在以下代码中,BlobTrigger 特性的路径创建名为 filename 表达式。 用于输出绑定时,filename 解析为触发 Blob 的名称。

public static void CreateThumbnail(
    [BlobTrigger("sample-images/{filename}")] Stream image,
    [Blob("sample-images-sm/{filename}", FileAccess.Write)] Stream imageSmall,
    string filename,
    ILogger logger)
{
    logger.Info($"Blob trigger processing: {filename}");
    // ...
}

有关绑定表达式的详细信息,请参阅 Azure Functions 文档中的绑定表达式和模式

自定义绑定表达式

有时,你想要在代码中指定队列名称、Blob 名称、容器或表名称,而不是进行硬编码。 例如,可能要在配置文件或环境变量中指定 QueueTrigger 特性的队列名称。

可以通过在配置期间传递自定义名称解析程序,来向属性分配队列名称。 在触发器或绑定特性构造函数参数中包含占位符,解析程序代码将提供用于取代这些占位符的实际值。 可以用百分号 (%) 括住占位符,以此对其进行标识:

public static void WriteLog([QueueTrigger("%logqueue%")] string logMessage)
{
    Console.WriteLine(logMessage);
}

此代码在测试环境中使用名为 logqueuetest 的队列,在生产环境中使用名为 logqueueprod 的队列。 在 appSettings 集合中指定条目名称,而不是硬编码的队列名称。

如果未提供自定义解析程序,则默认解析程序会生效。 默认设置从应用设置或环境变量中获取值。

从 .NET Core 3.1 开始, ConfigurationManager 使用的实例需要 System.Configuration.ConfigurationManager NuGet 包。 示例需要以下 using 语句:

using System.Configuration;

NameResolver 类从应用设置获取队列名称:

public class CustomNameResolver : INameResolver
{
    public string Resolve(string name)
    {
        return ConfigurationManager.AppSettings[name].ToString();
    }
}

版本 3.x

使用依赖关系注入配置解析程序。 这些示例需要下列 using 语句:

using Microsoft.Extensions.DependencyInjection;

可以通过调用 ConfigureServices 上的 HostBuilder 扩展方法来添加解析程序,如下例所示:

static async Task Main(string[] args)
{
    var builder = new HostBuilder();
    var resolver = new CustomNameResolver();
    builder.ConfigureWebJobs(b =>
    {
        b.AddAzureStorageCoreServices();
    });
    builder.ConfigureServices(s => s.AddSingleton<INameResolver>(resolver));
    var host = builder.Build();
    using (host)
    {
        await host.RunAsync();
    }
}

版本 2.x

NameResolver 类传入 JobHost 对象:

 static void Main(string[] args)
{
    JobHostConfiguration config = new JobHostConfiguration();
    config.NameResolver = new CustomNameResolver();
    JobHost host = new JobHost(config);
    host.RunAndBlock();
}

Azure Functions 实现 INameResolver 以从应用设置中获取值,如以下示例中所示。 直接使用 WebJobs SDK 时,可以编写一个自定义实现,用于从偏好的任何源获取占位符替代值。

在运行时绑定

如果需要在使用 QueueBlobTable 等绑定特性之前在函数中执行某项操作,可以使用 IBinder 接口。

以下示例使用输入队列消息,并在输出队列中创建具有相同内容的新消息。 输出队列名称由函数正文中的代码设置。

public static void CreateQueueMessage(
    [QueueTrigger("inputqueue")] string queueMessage,
    IBinder binder)
{
    string outputQueueName = "outputqueue" + DateTime.Now.Month.ToString();
    QueueAttribute queueAttribute = new QueueAttribute(outputQueueName);
    CloudQueue outputQueue = binder.Bind<CloudQueue>(queueAttribute);
    outputQueue.AddMessageAsync(new CloudQueueMessage(queueMessage));
}

有关详细信息,请参阅 Azure Functions 文档中的运行时绑定

绑定参考信息

Azure Functions 文档中提供了有关每个绑定类型的参考信息。 每篇绑定参考文章中都提供了以下信息。 (此示例基于某个存储队列。)

  • 。 需要安装哪个包才能在 WebJobs SDK 项目中支持绑定。
  • 示例。 代码示例。 C# 类库示例适用于 WebJobs SDK。 只需省略 FunctionName 特性。
  • 特性。 用于绑定类型的特性。
  • 配置。 特性属性和构造函数参数的解释。
  • 使用情况。 可绑定到哪些类型,以及有关绑定工作原理的信息。 例如:轮询算法或有害队列处理。

注意

HTTP、Webhook 和事件网格绑定仅受 Azure Functions 的支持,而不受 WebJobs SDK 的支持。

有关 Azure Functions 运行时支持的绑定的完整列表,请参阅 支持的绑定

属性:Disable、Timeout 和 Singleton

可以使用这些属性来控制函数触发、取消函数,并确保只有一个函数实例运行。

Disable 特性

使用 Disable 属性来控制是否可以触发某个函数。

在以下示例中,如果应用设置 Disable_TestJob 的值为 1True(不区分大小写),则该函数不会运行。 在这种情况下,运行时将创建日志消息“函数 'Functions.TestJob' 已禁用”。

[Disable("Disable_TestJob")]
public static void TestJob([QueueTrigger("testqueue2")] string message)
{
    Console.WriteLine("Function with Disable attribute executed!");
}

在 Azure 门户中更改应用设置值时,WebJob 会重启并选取新的设置。

可以在参数、方法或类级别声明该特性。 设置名称还可以包含绑定表达式。

Timeout 特性

如果某个函数在指定的时间段内未完成,则 Timeout 特性会导致该函数被取消。 在以下示例中,如果没有 Timeout 属性,该函数将运行一天。 如果指定了 Timeout,该函数将在 15 秒后被取消。 当 Timeout 特性throwOnError参数设置为true时,如果超出超时间隔,WebJobs SDK 将引发异常并终止函数调用。 throwOnError 的默认值为 false。 使用 Timeout 属性时,默认行为是通过设置取消标记来取消函数调用,同时允许该调用无限期地运行,直到函数代码返回异常或引发异常。

[Timeout("00:00:15")]
public static async Task TimeoutJob(
    [QueueTrigger("testqueue2")] string message,
    CancellationToken token,
    TextWriter log)
{
    await log.WriteLineAsync("Job starting");
    await Task.Delay(TimeSpan.FromDays(1), token);
    await log.WriteLineAsync("Job completed");
}

可以在类级别或方法级别应用Timeout属性,并且可以使用JobHostConfiguration.FunctionTimeout指定全局超时。 类级别或方法级别的超时替代全局超时。

Singleton 特性

Singleton 特性可确保即使有多个主机 Web 应用的实例,也只有一个函数实例运行。 该 Singleton 属性使用 分布式锁定 来确保仅运行一个实例。

在此示例中,在任意给定时间只会运行 ProcessImage 函数的单个实例:

[Singleton]
public static async Task ProcessImage([BlobTrigger("images")] Stream image)
{
     // Process the image.
}

SingletonMode.Listener

某些触发器为并发管理提供内置支持:

  • QueueTrigger。 将 JobHostConfiguration.Queues.BatchSize 设置为 1
  • ServiceBusTrigger。 将 ServiceBusConfiguration.MessageOptions.MaxConcurrentCalls 设置为 1
  • FileTrigger。 将 FileProcessor.MaxDegreeOfParallelism 设置为 1

可以使用这些设置来确保函数在单个实例上作为单一实例运行。 若要确保在 Web 应用横向扩展到多个实例时只运行函数的单个实例,请对该函数应用侦听器级别的单一实例锁 ([Singleton(Mode = SingletonMode.Listener)])。 启动 JobHost 时获取侦听器锁。 如果三个横向扩展的实例全部同时启动,只有其中的一个实例获取该锁,并且只有一个侦听器启动。

注意

若要详细了解工作原理 SingletonMode.Function ,请参阅 SingletonMode GitHub 存储库

范围值

可以在单一实例中指定一个范围表达式/值。 表达式/值可确保在特定范围内该函数的所有执行过程都将被序列化。 以这种方式实现更细化的锁定可以为函数提供一定程度的并行度,同时根据你的需求串行化其他调用。 例如,在以下代码中,范围表达式将绑定到传入消息的 Region 值。 当队列包含 East、East 和 West 区域中的三个消息时,将串行运行区域为 East 的消息。 区域 West 的消息与区域 East 的消息并行运行。

[Singleton("{Region}")]
public static async Task ProcessWorkItem([QueueTrigger("workitems")] WorkItem workItem)
{
     // Process the work item.
}

public class WorkItem
{
     public int ID { get; set; }
     public string Region { get; set; }
     public int Category { get; set; }
     public string Description { get; set; }
}

SingletonScope.Host

锁定的默认范围为 SingletonScope.Function。 锁定范围(Blob 租约路径)与完全限定的函数名称相关联。 若要跨函数锁定,请指定 SingletonScope.Host,并使用在不想要同时运行的所有函数中相同的范围 ID 名称。 在以下示例中,每次只会运行 AddItemRemoveItem 的一个实例:

[Singleton("ItemsLock", SingletonScope.Host)]
public static void AddItem([QueueTrigger("add-item")] string message)
{
     // Perform the add operation.
}

[Singleton("ItemsLock", SingletonScope.Host)]
public static void RemoveItem([QueueTrigger("remove-item")] string message)
{
     // Perform the remove operation.
}

查看租约 Blob

WebJobs SDK 使用 Azure Blob 租约 来实现分布式锁定。 可以在 Singleton 存储帐户的 azure-webjobs-host 容器中的路径“locks”下面找到 AzureWebJobsStorage 使用的租约 Blob。例如,前面演示的第一个 ProcessImage 示例的租约 Blob 路径可能是 locks/061851c758f04938a4426aa9ab3869c0/WebJobs.Functions.ProcessImage。 所有路径包含 JobHost ID,在本例中为 061851c758f04938a4426aa9ab3869c0。

异步函数

有关如何编写异步函数代码的信息,请参阅 Azure Functions 文档

取消令牌

有关如何处理取消令牌的信息,请参阅有关取消令牌和正常关闭的 Azure Functions 文档。

多个实例

如果 Web 应用在多个实例上运行,则会有一个连续的 WebJob 在每个实例上运行,并侦听触发器和调用函数。 各种触发器绑定旨在以协作方式有效分担各个实例上的工作,以便横向扩展到多个实例后可以处理更多的负载。

虽然某些触发器可能会导致重复处理,但队列和 Blob 存储触发器自动阻止函数多次处理队列消息或 Blob。 有关详细信息,请参阅 Azure Functions 文档中的针对完全相同的输入进行设计

计时器触发器会自动确保只会运行计时器的一个实例,因此,在给定的计划时间,不会运行多个函数实例。

如果你要确保即使有多个主机 Web 应用的实例,也只有一个函数实例运行,可以使用 Singleton 属性。

筛选器

通过函数筛选器(预览版)可以使用自己的逻辑自定义 WebJobs 执行管道。 这些筛选器类似于 ASP.NET 核心筛选器。 可将其实现为应用到函数或类的声明性特性。 有关详细信息,请参阅函数筛选器

日志记录和监视

建议使用为 ASP.NET 开发的日志记录框架。 入门文章中介绍了其用法。

日志筛选

ILogger 实例创建的每个日志都有关联的 CategoryLevel 值。 LogLevel 是一个枚举,整数代码指示相对重要性:

LogLevel 代码
跟踪 0
调试 1
信息 2
警告 3
错误 4
严重 5
6

可以根据特定的 LogLevel 值单独筛选每个类别。 例如,你可能想要查看有关 Blob 触发器处理的所有日志,但对于其他任何操作,只想查看 Error 和更高级别的日志。

版本 3.x

版本 3。x 的 SDK 依赖于内置于 .NET Core 中的筛选。 使用 LogCategories 类可为特定的函数、触发器或用户定义类别。 LogCategories 类还定义了针对特定主机状态的筛选器,例如 StartupResults,以便你可以微调日志输出。 如果在定义的类别中找不到匹配项,则筛选器在确定是否筛选消息时回退到 Default 值。

LogCategories 需要以下 using 语句:

using Microsoft.Azure.WebJobs.Logging; 

以下示例构造的筛选器默认会筛选 Warning 级别的所有日志。 Functionresults类别(等效于版本 2.Host.Results 中的 )在 Error 级别进行筛选。 筛选器将当前类别与 LogCategories 实例中所有已注册的级别进行比较,并选择最长匹配项。 因此为 Debug 注册的 Host.Triggers 级别将与 Host.Triggers.QueueHost.Triggers.Blob 匹配。 你可以控制更广泛的类别,而无需添加每个类别。

static async Task Main(string[] args)
{
    var builder = new HostBuilder();
    builder.ConfigureWebJobs(b =>
    {
        b.AddAzureStorageCoreServices();
    });
    builder.ConfigureLogging(logging =>
            {
                logging.SetMinimumLevel(LogLevel.Warning);
                logging.AddFilter("Function", LogLevel.Error);
                logging.AddFilter(LogCategories.CreateFunctionCategory("MySpecificFunctionName"),
                    LogLevel.Debug);
                logging.AddFilter(LogCategories.Results, LogLevel.Error);
                logging.AddFilter("Host.Triggers", LogLevel.Debug);
            });
    var host = builder.Build();
    using (host)
    {
        await host.RunAsync();
    }
}

版本 2.x

在版本 2.x 的 SDK 中,LogCategoryFilter 类用于控制筛选。 LogCategoryFilter 包含初始值为 DefaultInformation 属性,这意味着,将会记录级别为 InformationWarningErrorCritical 的所有消息,但会筛选掉级别为 DebugTrace 的所有消息。

与版本 3.LogCategories 中的 一样,使用 CategoryLevels 属性可以指定特定类别的日志级别,以便能够微调日志记录输出。 如果在字典中 CategoryLevels 找不到匹配项,则筛选器在确定是否筛选消息时回退到 Default 值。

以下示例构造的筛选器默认会筛选 Warning 级别的所有日志。 FunctionHost.Results 类别在 Error 级别进行筛选。 LogCategoryFilter 将当前类别与所有已注册的 CategoryLevels 进行比较,并选择最长匹配项。 因此为 Debug 注册的 Host.Triggers 级别将与 Host.Triggers.QueueHost.Triggers.Blob 匹配。 你可以控制更广泛的类别,而无需添加每个类别。

var filter = new LogCategoryFilter();
filter.DefaultLevel = LogLevel.Warning;
filter.CategoryLevels[LogCategories.Function] = LogLevel.Error;
filter.CategoryLevels[LogCategories.Results] = LogLevel.Error;
filter.CategoryLevels["Host.Triggers"] = LogLevel.Debug;

config.LoggerFactory = new LoggerFactory()
    .AddApplicationInsights(instrumentationKey, filter.Filter)
    .AddConsole(filter.Filter);

Application Insights 的自定义遥测

Application Insights 实现自定义遥测的过程取决于 SDK 版本。 要了解如何配置 Application Insights,请参阅添加 Application Insights 日志记录

版本 3.x

由于 WebJobs SDK 的版本 3.x 依赖于 .NET Core 通用主机,因此不再提供自定义遥测工厂。 但可以使用依赖关系注入将自定义遥测添加到管道。 本部分中的示例要求使用下列 using 语句:

using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.ApplicationInsights.Channel;

你可以使用 ITelemetryInitializer 的以下自定义实现将自己的 ITelemetry 实例添加到默认的 TelemetryConfiguration

internal class CustomTelemetryInitializer : ITelemetryInitializer
{
    public void Initialize(ITelemetry telemetry)
    {
        // Do something with telemetry.
    }
}

在生成器中调用 ConfigureServices,以将自定义 ITelemetryInitializer 实例添加到管道。

static async Task Main()
{
    var builder = new HostBuilder();
    builder.ConfigureWebJobs(b =>
    {
        b.AddAzureStorageCoreServices();
    });
    builder.ConfigureLogging((context, b) =>
    {
        // Add logging providers.
        b.AddConsole();

        // If this key exists in any config, use it to enable Application Insights.
        string appInsightsKey = context.Configuration["APPINSIGHTS_INSTRUMENTATIONKEY"];
        if (!string.IsNullOrEmpty(appInsightsKey))
        {
            // This code uses the options callback to explicitly set the instrumentation key.
            b.AddApplicationInsights(o => o.InstrumentationKey = appInsightsKey);
        }
    });
    builder.ConfigureServices(services =>
        {
            services.AddSingleton<ITelemetryInitializer, CustomTelemetryInitializer>();
        });
    var host = builder.Build();
    using (host)
    {
        await host.RunAsync();
    }
}

构造 TelemetryConfiguration 时,将包含所有已注册的 ITelemetryInitializer 类型。 有关详细信息,请参阅 用于自定义事件和指标的 Application Insights API

在版本 3.x 中,主机停止时无需刷新 TelemetryClient。 .NET Core 依赖项注入系统会自动释放已注册的 ApplicationInsightsLoggerProvider 实例,从而刷新 TelemetryClient

版本 2.x

在版本 2.x 中,Application Insights 提供程序在内部为 WebJobs SDK 创建的 实例使用 TelemetryClientServerTelemetryChannel。 当 Application Insights 终结点不可用或正在限制传入请求时,此通道将 请求保存在 Web 应用的文件系统中,并在以后重新提交请求

TelemetryClient 是实现 ITelemetryClientFactory 的类创建的。 默认情况下,此类为 DefaultTelemetryClientFactory

如果你要修改 Application Insights 管道的任何部分,可以提供自己的 ITelemetryClientFactory 实例。 然后,主机将使用你的类来构造 TelemetryClient。 例如,此代码会替代 DefaultTelemetryClientFactory 来修改 ServerTelemetryChannel 的属性:

private class CustomTelemetryClientFactory : DefaultTelemetryClientFactory
{
    public CustomTelemetryClientFactory(string instrumentationKey, Func<string, LogLevel, bool> filter)
        : base(instrumentationKey, new SamplingPercentageEstimatorSettings(), filter)
    {
    }

    protected override ITelemetryChannel CreateTelemetryChannel()
    {
        ServerTelemetryChannel channel = new ServerTelemetryChannel();

        // Change the default from 30 seconds to 15 seconds.
        channel.MaxTelemetryBufferDelay = TimeSpan.FromSeconds(15);

        return channel;
    }
}

SamplingPercentageEstimatorSettings 对象配置自适应采样。 在这种情况下,在某些大容量方案中,Application Insights 会向服务器发送选定的遥测数据子集。

创建遥测工厂后,可将其传入 Application Insights 日志记录提供程序:

var clientFactory = new CustomTelemetryClientFactory(instrumentationKey, filter.Filter);

config.LoggerFactory = new LoggerFactory()
    .AddApplicationInsights(clientFactory);

相关内容

本文提供了代码片段,演示如何处理使用 WebJobs SDK 的常见方案。 有关完整示例,请参阅 azure-webjobs-sdk-samples