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

本文提供有关如何使用 Azure WebJobs SDK 的指导。 若想马上开始使用 WebJobs,请参阅 Azure WebJobs SDK 入门

WebJobs SDK 版本

下面是 WebJobs SDK 版本 3.x 与版本 2.x 之间的重要差别:

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

本文中的多处说明提供了适用于 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 主机

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

这是直接使用 WebJobs SDK 与通过 Azure Functions 间接使用它的主要差别。 在 Azure Functions 中,由于由服务控制主机,因此无法通过编写代码来定义主机。 Azure Functions 允许通过 host.json 文件中的设置自定义主机行为。 这些设置是字符串而不是代码,使用这些字符串会限制可执行的自定义类型。

主机连接字符串

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

版本 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 Core API。 对 HostBuilder 实例调用 UseEnvironment 方法。 传递名为 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 中,连接限制默认为无限次连接。 如果出于某种原因需要更改此限制,则可以使用 WinHttpHandler 类的 MaxConnectionsPerServer 属性。

在版本 2.x 中,使用 ServicePointManager.DefaultConnectionLimit API 控制主机的并发连接数。 在 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,这可能非常适合在“基本”或更高级别应用服务计划中运行的 WebJob。 WebJobs 通常需要 Always On 设置,该设置仅受基本和更高级别应用服务计划的支持。

如果 WebJob 在“免费”或“共享”应用服务计划中运行,则应用程序会受到应用服务沙盒的限制:当前的连接限制为 300 个。 如果在 ServicePointManager 中指定无限制的连接数,则很有可能会达到沙盒连接阈值,并且站点将会关闭。 在这种情况下,将 DefaultConnectionLimit 设置为更小的值(例如 50 或 100)可以防止此问题发生,同时仍可保持足够的吞吐量。

必须在发出任何 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 队列存储的消息触发。 该函数通过从 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 或第三方服务中的数据提供给代码使用。 输出绑定提供更新数据的方式。 入门文章中演示了输入和输出绑定的示例。

通过将属性应用于方法返回值,可以对输出绑定使用方法返回值。 请参阅使用 Azure 函数返回值中的示例。

绑定类型

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

版本 3.x

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

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 包,并对 JobHostConfiguration 对象调用 Use<binding> 方法。 例如,若要使用 Timer 触发器,请安装 Microsoft.Azure.WebJobs.Extensions 并在 Main 方法中调用 UseTimers,如下所示:

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

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

ExecutionContext

使用 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

ConfigureWebJobs 方法中调用 AddExecutionContextBinding 扩展方法,如下所示:

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.xConfigureWebJobs 中调用 Add<Binding> 方法时设置配置。
  • 版本 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 绑定一文。

事件中心触发器配置(版本 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();
    }
}

有关更多详细信息,请参阅服务总线绑定一文。

其他绑定的配置

某些触发器和绑定类型定义其自身的自定义配置类型。 例如,File 触发器允许指定要监视的根路径,如以下示例中所示。

版本 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;

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

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 时获取侦听器锁。 如果三个横向扩展的实例全部同时启动,只有其中的一个实例获取该锁,并且只有一个侦听器启动。

注意

请参阅此 GitHub 存储库,详细了解 SingletonMode.Function 的工作原理。

范围值

可以在单一实例中指定一个范围表达式/值。 表达式/值可确保特定范围内的所有函数执行都将序列化。 以这种方式实现更细化的锁定可以为函数提供一定程度的并行度,同时根据你的需求串行化其他调用。 例如,在以下代码中,范围表达式将绑定到传入消息的 Region 值。 队列包含 East、East 和 North 区域中的三个消息时,将串行运行区域为 East 的消息。 区域为 North 的消息将与区域 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 租约来实现分布式锁定。 可以在 AzureWebJobsStorage 存储帐户的 azure-webjobs-host 容器中的路径“locks”下面找到单一实例使用的租约 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 Core 筛选器。 可将其实现为应用到函数或类的声明性特性。 有关详细信息,请参阅函数筛选器

日志记录和监视

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

日志筛选

ILogger 实例创建的每个日志都包含关联的 CategoryLevelLogLevel 是一个枚举,整数代码指示相对重要性:

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

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

版本 3.x

版本 3.x 的 SDK 依赖于 .NET Core 内置的筛选。 使用 LogCategories 类,可以为特定函数、触发器或用户定义类别。 它还能为特定主机状态(例如,StartupResults)定义筛选器。 这样就可以微调日志记录输出。 如果在定义类别中未找到任何匹配项,筛选器在决定是否筛选消息时会回退到 Default 值。

LogCategories 需要以下 using 语句:

using Microsoft.Azure.WebJobs.Logging; 

以下示例构造的筛选器默认会筛选 Warning 级别的所有日志。 Functionresults类别(等效于版本 2.x 中的 Host.Results)在 Error 级别进行筛选。 筛选器将当前类别与 LogCategories 实例中所有已注册的级别进行比较,并选择最长匹配项。 这意味着,为 Host.Triggers 注册的 Debug 级别将匹配 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 包含初始值为 InformationDefault 属性,这意味着,将会记录级别为 InformationWarningErrorCritical 的所有消息,但会筛选掉级别为 DebugTrace 的所有消息。

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

以下示例构造的筛选器默认会筛选 Warning 级别的所有日志。 FunctionHost.Results 类别在 Error 级别进行筛选。 LogCategoryFilter 将当前类别与所有已注册的 CategoryLevels 进行比较,并选择最长匹配项。 因此,为 Host.Triggers 注册的 Debug 级别将匹配 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 的自定义实现,可向默认的 添加自己的 TelemetryConfigurationITelemetry

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 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 在内部创建的 TelemetryClient 使用 ServerTelemetryChannel。 当 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