有关在隔离进程中运行 C# Azure Functions 的指南

本文介绍如何使用 C# 开发 .NET 隔离进程函数,这些函数在 Azure Functions 中的进程外运行。 通过进程外的运行,可将函数代码与 Azure Functions 运行时分离开来。 独立进程 C# 函数在 .NET 6.0、.NET 7.0 和 .NET Framework 4.8(预览版支持)上运行。 .NET 7.0 不支持进程内 C# 类库函数

入门 概念 示例

为何使用 .NET 隔离进程?

过去,Azure Functions 仅支持 .NET 函数的紧密集成模式,这种模式在主机所用的同一进程中作为类库运行。 此模式在主机进程与函数之间提供深度集成。 例如,.NET 类库函数可以共享绑定 API 和类型。 但是,这种集成还要求在主机进程与 .NET 函数之间实现更紧密的耦合。 例如,在进程内运行的 .NET 函数需要在 Functions 运行时所用的同一 .NET 版本上运行。 为了能够摆脱这些约束顺利运行,现在可以选择在隔离的进程中运行。 借助这种进程隔离,还可以开发使用当前 .NET 版本(例如 .NET 7.0)且不被 Functions 运行时原生支持的函数。 隔离进程和进程内 C# 类库函数都在 .NET 6.0 上运行。 若要了解详细信息,请参阅支持的版本

由于这些函数在独立的进程中运行,.NET 隔离的函数应用与 .NET 类库函数应用之间存在一些特性和功能差异

在进程外运行的优势

在进程外运行 .NET 函数时,可利用以下优势:

  • 减少冲突:由于函数在独立的进程中运行,因此应用中使用的程序集不会与主机进程所用的相同程序集的不同版本发生冲突。
  • 全面控制进程:可以控制应用的启动,并可控制所用的配置和启动的中间件。
  • 依赖项注入:由于可以全面控制进程,因此可以使用当前的 .NET 依赖项注入行为,并将中间件整合到函数应用中。

支持的版本

Functions 运行时版本使用特定版本的 .NET。 若要详细了解 Functions 版本,请参阅 Azure Functions 运行时版本概述。 版本支持取决于 Functions 是在进程内运行还是在进程外(独立)运行。

注意

若要了解如何更改函数应用使用的 Functions 运行时版本,请参阅查看和更新当前运行时版本

下表显示了可与特定版本的 Functions 配合使用的 .NET Core 或 .NET Framework 的最高级别。

Functions 运行时版本 进程内
.NET 类库
进程外
.NET 独立
Functions 4.x .NET 6.0 .NET 6.0
.NET Framework 4.8(预览版)1
Functions 3.x .NET Core 3.1 .NET 5.02
Functions 2.x .NET Core 2.13 不适用
Functions 1.x .NET Framework 4.8 不适用

1 生成过程还需要使用 .NET 6 SDK。 对 .NET Framework 4.8 的支持处于预览状态。

2 生成过程还需要使用 .NET Core 3.1 SDK

3 有关详细信息,请参阅 Functions v2.x 注意事项

有关 Azure Functions 版本的最新消息,包括删除较旧的特定次要版本,请关注 Azure 应用服务公告

.NET 隔离项目

简单而言,.NET 隔离函数项目是一个 .NET 控制台应用项目,它面向受支持的 .NET 运行时。 下面是任何 .NET 隔离项目中所需的基本文件:

有关完整示例,请参阅 .NET 6 独立示例项目.NET Framework 4.8 独立示例项目

注意

若要将独立函数项目发布到 Azure 中的 Windows 或 Linux 函数应用,必须在远程 FUNCTIONS_WORKER_RUNTIME 应用程序设置中设置值“dotnet-isolated”。 若要在 Linux 上支持 zip 部署以及从部署包运行,还需要将 linuxFxVersion 站点配置设置更新为 DOTNET-ISOLATED|7.0。 若要了解详情,请参阅 Linux 上的手动版本更新

包引用

在进程外运行函数时,.NET 项目将使用一组唯一的包,这些包实现核心功能和绑定扩展。

核心包

需要使用以下包在隔离的进程中运行 .NET 函数:

扩展包

由于在 .NET 隔离进程中运行的函数使用不同的绑定类型,因此它们需要一组唯一的绑定扩展包。

可以在 Microsoft.Azure.Functions.Worker.Extensions 下找到这些扩展包。

启动和配置

使用 .NET 隔离函数时,可以访问函数应用的启动代码(通常在 Program.cs 中)。 你需要负责创建并启动自己的主机实例。 因此,你还可以直接访问应用的配置管道。 在进程外运行函数时,添加配置、注入依赖项和运行自己的中间件的操作要容易得多。

以下代码显示了 HostBuilder 管道的示例:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace FunctionApp
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // #if DEBUG
            //          Debugger.Launch();
            // #endif
            //<docsnippet_startup>
            var host = new HostBuilder()
                //<docsnippet_configure_defaults>
                .ConfigureFunctionsWorkerDefaults(builder =>
                {
                    builder
                        .AddApplicationInsights()
                        .AddApplicationInsightsLogger();
                })
                //</docsnippet_configure_defaults>
                //<docsnippet_dependency_injection>
                .ConfigureServices(s =>
                {
                    s.AddSingleton<IHttpResponderService, DefaultHttpResponderService>();
                })
                //</docsnippet_dependency_injection>
                .Build();
            //</docsnippet_startup>

            //<docsnippet_host_run>
            await host.RunAsync();
            //</docsnippet_host_run>
        }
    }
}

此代码需要 using Microsoft.Extensions.DependencyInjection;

HostBuilder 用于生成并返回已完全初始化的 IHost 实例,该实例以异步方式运行以启动函数应用。

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace FunctionApp
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // #if DEBUG
            //          Debugger.Launch();
            // #endif
            //<docsnippet_startup>
            var host = new HostBuilder()
                //<docsnippet_configure_defaults>
                .ConfigureFunctionsWorkerDefaults(builder =>
                {
                    builder
                        .AddApplicationInsights()
                        .AddApplicationInsightsLogger();
                })
                //</docsnippet_configure_defaults>
                //<docsnippet_dependency_injection>
                .ConfigureServices(s =>
                {
                    s.AddSingleton<IHttpResponderService, DefaultHttpResponderService>();
                })
                //</docsnippet_dependency_injection>
                .Build();
            //</docsnippet_startup>

            //<docsnippet_host_run>
            await host.RunAsync();
            //</docsnippet_host_run>
        }
    }
}

重要

如果项目面向 .NET Framework 4.8,则还需要在创建 HostBuilder 之前添加 FunctionsDebugger.Enable();。 它应该是方法 Main() 的第一行。 有关详细信息,请参阅面向 .NET Framework 时的调试

配置

ConfigureFunctionsWorkerDefaults 方法用于添加函数应用在进程外运行所需的设置,包括以下功能:

  • 转换器的默认设置。
  • 设置默认 JsonSerializerOptions 以忽略属性名称大小写。
  • 与 Azure Functions 日志记录集成。
  • 输出绑定中间件和功能。
  • 函数执行中间件。
  • 默认的 gRPC 支持。
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace FunctionApp
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // #if DEBUG
            //          Debugger.Launch();
            // #endif
            //<docsnippet_startup>
            var host = new HostBuilder()
                //<docsnippet_configure_defaults>
                .ConfigureFunctionsWorkerDefaults(builder =>
                {
                    builder
                        .AddApplicationInsights()
                        .AddApplicationInsightsLogger();
                })
                //</docsnippet_configure_defaults>
                //<docsnippet_dependency_injection>
                .ConfigureServices(s =>
                {
                    s.AddSingleton<IHttpResponderService, DefaultHttpResponderService>();
                })
                //</docsnippet_dependency_injection>
                .Build();
            //</docsnippet_startup>

            //<docsnippet_host_run>
            await host.RunAsync();
            //</docsnippet_host_run>
        }
    }
}

能够访问主机生成器管道意味着还可以在初始化期间设置任何特定于应用的配置。 可以一次或多次调用HostBuilder 中的 ConfigureAppConfiguration 方法来添加函数应用所需的配置。 若要详细了解应用配置,请参阅 ASP.NET Core 中的配置

这些配置将应用到在独立进程中运行的函数应用。 若要更改 Functions 主机或触发器以及绑定配置,仍需使用 host.json 文件

依赖关系注入

与 .NET 类库相比,依赖项注入已得到简化。 不必创建启动类来注册服务,而只需在主机生成器中调用 ConfigureServices,并使用 IServiceCollection 中的扩展方法来注入特定的服务。

以下示例注入单一实例服务依赖项:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace FunctionApp
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // #if DEBUG
            //          Debugger.Launch();
            // #endif
            //<docsnippet_startup>
            var host = new HostBuilder()
                //<docsnippet_configure_defaults>
                .ConfigureFunctionsWorkerDefaults(builder =>
                {
                    builder
                        .AddApplicationInsights()
                        .AddApplicationInsightsLogger();
                })
                //</docsnippet_configure_defaults>
                //<docsnippet_dependency_injection>
                .ConfigureServices(s =>
                {
                    s.AddSingleton<IHttpResponderService, DefaultHttpResponderService>();
                })
                //</docsnippet_dependency_injection>
                .Build();
            //</docsnippet_startup>

            //<docsnippet_host_run>
            await host.RunAsync();
            //</docsnippet_host_run>
        }
    }
}

此代码需要 using Microsoft.Extensions.DependencyInjection;。 有关详细信息,请参阅 ASP.NET Core 中的依赖项注入

中间件

.NET 隔离进程还支持中间件注册,这同样是使用与 ASP.NET 中类似的模型实现的。 使用此模型可以在执行函数之前和之后将逻辑注入到调用管道中。

ConfigureFunctionsWorkerDefaults 扩展方法具有一个重载,可让你注册自己的中间件,如以下示例中所示。

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Linq;
using Microsoft.Extensions.Hosting;

namespace CustomMiddleware
{
    public class Program
    {
        public static void Main()
        {
            //<docsnippet_middleware_register>
            var host = new HostBuilder()
                .ConfigureFunctionsWorkerDefaults(workerApplication =>
                {
                    // Register our custom middlewares with the worker

                    workerApplication.UseMiddleware<ExceptionHandlingMiddleware>();

                    workerApplication.UseMiddleware<MyCustomMiddleware>();

                    workerApplication.UseWhen<StampHttpHeaderMiddleware>((context) =>
                    {
                        // We want to use this middleware only for http trigger invocations.
                        return context.FunctionDefinition.InputBindings.Values
                                      .First(a => a.Type.EndsWith("Trigger")).Type == "httpTrigger";
                    });
                })
                .Build();
            //</docsnippet_middleware_register>
            host.Run();
        }
    }
}

UseWhen 扩展方法可用于注册有条件执行的中间件。 返回布尔值的谓词需要传递给此方法,如果谓词的返回值为 true,中间件将参与调用处理管道。

利用 FunctionContext 上的以下扩展方法可在独立模型中更轻松地使用中间件。

方法 说明
GetHttpRequestDataAsync 在被 HTTP 触发器调用时获取 HttpRequestData 实例。 此方法返回 ValueTask<HttpRequestData?> 的实例,当你要读取消息数据(例如请求标头和 Cookie)时,该实例很有用。
GetHttpResponseData 在被 HTTP 触发器调用时获取 HttpResponseData 实例。
GetInvocationResult 获取 InvocationResult 的实例,该实例表示当前函数执行的结果。 根据需要使用 Value 属性获取或设置值。
GetOutputBindings 获取当前函数执行的输出绑定条目。 此方法结果中的每个条目的类型都是 OutputBindingData。 可以根据需要使用 Value 属性获取或设置值。
BindInputAsync 为请求的 BindingMetadata 实例绑定一个输入绑定项。 例如,当函数的 BlobInput 输入绑定需要由中间件访问或更新时,可以使用此方法。

下面是一个中间件实现的示例,它在函数执行期间读取 HttpRequestData 实例并更新 HttpResponseData 实例。 此中间件检查是否存在特定请求标头 (x-correlationId),如果存在,则此中间件将使用该标头值标记响应标头。 否则,它会生成一个新的 GUID 值,并使用该值来标记响应标头。

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Middleware;

namespace CustomMiddleware
{
    /// <summary>
    /// Middleware to stamp a response header on the result of http trigger invocation.
    /// </summary>
    //<docsnippet_middleware_example_stampheader>
    internal sealed class StampHttpHeaderMiddleware : IFunctionsWorkerMiddleware
    {
        public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
        {
            var requestData = await context.GetHttpRequestDataAsync();

            string correlationId;
            if (requestData!.Headers.TryGetValues("x-correlationId", out var values))
            {
                correlationId = values.First();
            }
            else
            {
                correlationId = Guid.NewGuid().ToString();
            }

            await next(context);

            context.GetHttpResponseData()?.Headers.Add("x-correlationId", correlationId);
        }
    }
    //</docsnippet_middleware_example_stampheader>
}

有关在函数应用中使用自定义中间件的更完整示例,请参阅自定义中间件参考示例

执行上下文

.NET 隔离进程将 FunctionContext 对象传递给函数方法。 使用此对象可以通过调用 GetLogger 方法并提供 categoryName 字符串,来获取要写入到日志的 ILogger 实例。 有关详细信息,请参阅日志记录

绑定

绑定是通过在方法、参数和返回类型中使用特性定义的。 函数方法是包含 Function 特性以及一个应用于输入参数的触发器特性的方法,如以下示例中所示:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace SampleApp
{
    public static class QueueFunction
    {
        //<docsnippet_queue_output_binding>
        //<docsnippet_queue_trigger>
        [Function("QueueFunction")]
        [QueueOutput("output-queue")]
        public static string[] Run([QueueTrigger("input-queue")] Book myQueueItem,

            FunctionContext context)
        //</docsnippet_queue_trigger>
        {
            // Use a string array to return more than one message.
            string[] messages = {
                $"Book name = {myQueueItem.Name}",
                $"Book ID = {myQueueItem.Id}"};
            var logger = context.GetLogger("QueueFunction");
            logger.LogInformation($"{messages[0]},{messages[1]}");

            // Queue Output messages
            return messages;
        }
        //</docsnippet_queue_output_binding>
    }

    public class Book
    {
        public string Name { get; set; }

        public string Id { get; set; }
    }
}

触发器属性指定触发器类型并将输入数据绑定到一个方法参数。 以上示例函数将由一条队列消息触发,队列消息将传递给方法中的 myQueueItem 参数。

Function 属性将该方法标记为函数入口点。 该名称在项目中必须是唯一的,以字母开头,并且只包含字母、数字、_-,长度不得超过 127 个字符。 项目模板通常创建一个名为 Run 的方法,但方法名称可以是任何有效的 C# 方法名称。

由于 .NET 隔离项目在独立的工作进程中运行,因此绑定无法利用丰富的绑定类,例如 ICollector<T>IAsyncCollector<T>CloudBlockBlob。 此外,不直接支持继承自底层服务 SDK 的类型,例如 DocumentClientBrokeredMessage。 绑定依赖于字符串、数组和可序列化类型,例如普通旧类对象 (POCO)。

对于 HTTP 触发器,必须使用 HttpRequestDataHttpResponseData 来访问请求与响应数据。 这是因为,在进程外运行时,无法访问原始 HTTP 请求和响应对象。

有关在进程外运行时使用触发器和绑定的一组完整参考示例,请参阅绑定扩展参考示例

输入绑定

一个函数可以有零个或多个可向函数传递数据的输入绑定。 与触发器一样,输入绑定是通过向输入参数应用绑定特性来定义的。 执行函数时,运行时将尝试获取绑定中指定的数据。 请求的数据通常依赖于触发器使用绑定参数提供的信息。

输出绑定

若要写入到输出绑定,必须将输出绑定特性应用到函数方法,该方法定义了如何写入到绑定的服务。 该方法返回的值将写入到输出绑定。 例如,以下示例使用输出绑定将一个字符串值写入到名为 myqueue-output 的消息队列:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace SampleApp
{
    public static class QueueFunction
    {
        //<docsnippet_queue_output_binding>
        //<docsnippet_queue_trigger>
        [Function("QueueFunction")]
        [QueueOutput("output-queue")]
        public static string[] Run([QueueTrigger("input-queue")] Book myQueueItem,

            FunctionContext context)
        //</docsnippet_queue_trigger>
        {
            // Use a string array to return more than one message.
            string[] messages = {
                $"Book name = {myQueueItem.Name}",
                $"Book ID = {myQueueItem.Id}"};
            var logger = context.GetLogger("QueueFunction");
            logger.LogInformation($"{messages[0]},{messages[1]}");

            // Queue Output messages
            return messages;
        }
        //</docsnippet_queue_output_binding>
    }

    public class Book
    {
        public string Name { get; set; }

        public string Id { get; set; }
    }
}

多个输出绑定

写入到输出绑定的数据始终是函数的返回值。 如果需要写入到多个输出绑定,必须创建自定义返回类型。 在此返回类型中,必须已将输出绑定特性应用到类的一个或多个属性。 以下来自 HTTP 触发器的示例写入到 HTTP 响应和队列输出绑定:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Net;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;

namespace SampleApp
{
    /// <summary>
    /// This class specifies output bindings in the properties of <see cref="MyOutputType"/>.
    /// <see cref="MyOutputType"/> defines a Queue output binding, and an Http Response property.
    /// By default, a property of type <see cref="HttpResponseData"/> in the return type of the function
    /// is treated as an Http output binding. This property can be used to provide a response to the Http trigger.
    /// </summary>
    //<docsnippet_multiple_outputs>
    public static class MultiOutput
    {
        [Function("MultiOutput")]
        public static MyOutputType Run([HttpTrigger(AuthorizationLevel.Anonymous, "get")] HttpRequestData req,
            FunctionContext context)
        {
            var response = req.CreateResponse(HttpStatusCode.OK);
            response.WriteString("Success!");

            string myQueueOutput = "some output";

            return new MyOutputType()
            {
                Name = myQueueOutput,
                HttpResponse = response
            };
        }
    }

    public class MyOutputType
    {
        [QueueOutput("myQueue")]
        public string Name { get; set; }

        public HttpResponseData HttpResponse { get; set; }
    }
    //</docsnippet_multiple_outputs>
}

来自 HTTP 触发器的响应始终被视为输出,因此不需要返回值属性。

HTTP 触发器

HTTP 触发器将传入的 HTTP 请求消息转换为要传递给函数的 HttpRequestData 对象。 此对象提供请求中的数据,包括 HeadersCookiesIdentitiesURL 和可选消息 Body。 此对象是 HTTP 请求对象的表示形式,而不是请求本身。

同样,函数返回一个 HttpReponseData 对象,该对象提供用于创建 HTTP 响应的数据,包括消息 StatusCodeHeaders 和可选消息 Body

以下代码是一个 HTTP 触发器

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Net;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;

namespace SampleApp
{
    public static class HttpFunction
    {
        //<docsnippet_http_trigger>
        [Function("HttpFunction")]
        public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req,
            FunctionContext executionContext)
        {
            //<docsnippet_logging>
            var logger = executionContext.GetLogger("HttpFunction");
            logger.LogInformation("message logged");
            //</docsnippet_logging>

            var response = req.CreateResponse(HttpStatusCode.OK);
            response.Headers.Add("Date", "Mon, 18 Jul 2016 16:06:00 GMT");
            response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
            
            response.WriteString("Welcome to .NET 5!!");

            return response;
        }
        //</docsnippet_http_trigger>
    }
}

日志记录

在 .NET 隔离进程中,可以使用 ILogger 实例写入日志,该实例是从传递给函数的 FunctionContext 对象获取的。 调用 GetLogger 方法并传递一个字符串值,该值是在其中写入日志的类别的名称。 该类别通常是从中写入日志的特定函数的名称。 若要详细了解类别,请参阅有关监视的文章

以下示例演示如何获取 ILogger 并在函数中写入日志:

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System.Net;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;

namespace SampleApp
{
    public static class HttpFunction
    {
        //<docsnippet_http_trigger>
        [Function("HttpFunction")]
        public static HttpResponseData Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req,
            FunctionContext executionContext)
        {
            //<docsnippet_logging>
            var logger = executionContext.GetLogger("HttpFunction");
            logger.LogInformation("message logged");
            //</docsnippet_logging>

            var response = req.CreateResponse(HttpStatusCode.OK);
            response.Headers.Add("Date", "Mon, 18 Jul 2016 16:06:00 GMT");
            response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
            
            response.WriteString("Welcome to .NET 5!!");

            return response;
        }
        //</docsnippet_http_trigger>
    }
}

使用 ILogger 的各种方法写入各种日志级别,例如 LogWarningLogError。 若要详细了解日志级别,请参阅有关监视的文章

使用依赖项注入时还会提供 ILogger

面向 .NET Framework 时的调试

如果独立项目面向 .NET Framework 4.8,则当前预览版范围需要你执行手动步骤才能启用调试。 如果使用其他目标框架,则不需要执行这些步骤。

应用应首先调用 FunctionsDebugger.Enable(); 作为其第一个操作。 在 Main() 方法中初始化 HostBuilder 之前会发生此操作。 Program.cs 文件的内容应类似于:

using System;
using System.Diagnostics;
using Microsoft.Extensions.Hosting;
using Microsoft.Azure.Functions.Worker;
using NetFxWorker;

namespace MyDotnetFrameworkProject
{
    internal class Program
    {
        static void Main(string[] args)
        {
            FunctionsDebugger.Enable();

            var host = new HostBuilder()
                .ConfigureFunctionsWorkerDefaults()
                .Build();

            host.Run();
        }
    }
}

接下来,需要使用 .NET Framework 调试程序手动附加到进程。 Visual Studio 还不会自动为 .NET Framework 应用的隔离进程执行此操作,应避免“开始调试”操作。

在项目目录(或其生成输出目录)中,运行:

func host start --dotnet-isolated-debug

这会启动你的工作进程,然后该进程将停止,并显示以下消息:

Azure Functions .NET Worker (PID: <process id>) initialized in debug mode. Waiting for debugger to attach...

其中 <process id> 是你的工作进程的 ID。 现在可以使用 Visual Studio 手动附加到该进程。 有关此操作的说明,请参阅如何附加到正在运行的进程

附加调试程序后,进程执行将恢复,你将能够进行调试。

与 .NET 类库函数之间的差异

本部分介绍进程外运行与 .NET 类库函数进程内运行在功能和行为上的当前差异状态:

功能/行为 进程内 进程外
.NET 版本 .NET Core 3.1
.NET 6.0
.NET 6.0
.NET 7.0(预览版)
.NET Framework 4.8(预览版)
核心包 Microsoft.NET.Sdk.Functions Microsoft.Azure.Functions.Worker
Microsoft.Azure.Functions.Worker.Sdk
绑定扩展包 Microsoft.Azure.WebJobs.Extensions.* Microsoft.Azure.Functions.Worker.Extensions.*
日志记录 传递给函数的 ILogger FunctionContext 获取的 ILogger
取消令牌 支持 不支持
输出绑定 输出参数 返回值
输出绑定类型 IAsyncCollectorDocumentClientBrokeredMessage 和其他特定于客户端的类型 简单类型、JSON 可序列化类型和数组。
多个输出绑定 支持 支持
HTTP 触发器 HttpRequest/ObjectResult HttpRequestData/HttpResponseData
Durable Functions 支持 支持(公共预览版)
命令性绑定 支持 不支持
function.json 项目 已生成 不生成
Configuration host.json host.json 和自定义初始化
依赖关系注入 支持 支持
中间件 不支持 支持
冷启动时间 典型 因为是即时启动,所以时间更长。 在 Linux 而不是 Windows 上运行,以减少潜在的延迟。
ReadyToRun 支持 TBD
Application Insights 依赖项 支持 不支持

使用 Visual Studio 进行远程调试

由于独立进程应用在 Functions 运行时外部运行,因此你需要将远程调试器附加到单独的进程。 要详细了解如何使用 Visual Studio 进行调试,请参阅远程调试

后续步骤