Guia para executar Funções do Azure C# num processo de trabalho isolado

Este artigo é uma introdução ao processo de trabalho isolado das Funções .NET, que executa as suas funções num processo de trabalho isolado no Azure. Isto permite-lhe executar as funções de biblioteca de classes .NET numa versão do .NET diferente da versão utilizada pelo processo de anfitrião de Funções. Para obter informações sobre versões específicas do .NET suportadas, consulte a versão suportada.

Utilize as seguintes ligações para começar imediatamente a criar funções de processo de trabalho isolado .NET.

Introdução Conceitos Amostras

Se ainda precisar de executar as suas funções no mesmo processo que o anfitrião, veja Funções de biblioteca de classes C# em processo.

Para obter uma comparação abrangente entre o processo de trabalho isolado e as Funções .NET em processo, veja Diferenças entre o processo em processo e o processo de trabalho isolado .NET Funções do Azure.

Por que motivo as Funções .NET isolaram o processo de trabalho?

Quando foi introduzida, Funções do Azure suportava apenas um modo totalmente integrado para funções .NET. Neste modo de processo , as funções da biblioteca de classes .NET são executadas no mesmo processo que o anfitrião. Este modo fornece uma integração profunda entre o processo de anfitrião e as funções. Por exemplo, ao executar no mesmo processo, as funções de biblioteca de classes .NET podem partilhar APIs e tipos de enlace. No entanto, esta integração também requer um acoplamento apertado entre o processo de anfitrião e a função .NET. Por exemplo, as funções .NET em execução no processo são necessárias para serem executadas na mesma versão do .NET que o runtime das Funções. Isto significa que as suas funções no processo só podem ser executadas na versão do .NET com Suporte de Longo Prazo (LTS). Para permitir a execução numa versão não LTS do .NET, pode optar por executar num processo de trabalho isolado. Este isolamento de processo permite-lhe desenvolver funções que utilizam versões .NET atuais não suportadas nativamente pelo runtime de Funções, incluindo .NET Framework. Tanto o processo de trabalho isolado como as funções de biblioteca de classes C# em processo são executadas em versões LTS. Para saber mais, veja Versões suportadas.

Uma vez que estas funções são executadas num processo separado, existem algumas diferenças de funcionalidades e funcionalidades entre aplicações de funções isoladas .NET e aplicações de funções de biblioteca de classes .NET.

Benefícios do processo de trabalho isolado

Quando as funções .NET são executadas num processo de trabalho isolado, pode tirar partido dos seguintes benefícios:

  • Menos conflitos: uma vez que as funções são executadas num processo separado, as assemblagens utilizadas na sua aplicação não entrarão em conflito com uma versão diferente das mesmas assemblagens utilizadas pelo processo de anfitrião.
  • Controlo total do processo: controla o arranque da aplicação e pode controlar as configurações utilizadas e o middleware iniciado.
  • Injeção de dependência: uma vez que tem controlo total do processo, pode utilizar os comportamentos atuais do .NET para injeção de dependências e incorporar middleware na sua aplicação de funções.

Versões suportadas

As versões do runtime de Funções funcionam com versões específicas do .NET. Para saber mais sobre as versões das Funções, veja Funções do Azure descrição geral das versões de runtime. O suporte da versão depende se as funções são executadas no processo ou no processo de trabalho isolado.

Nota

Para saber como alterar a versão de runtime das Funções utilizada pela sua aplicação de funções, consulte Ver e atualizar a versão atual do runtime.

A tabela seguinte mostra o nível mais alto de .NET Core ou .NET Framework que podem ser utilizados com uma versão específica das Funções.

Versão do runtime das funções Em processo
(Biblioteca de classes.NET)
Processo de trabalho isolado
(.NET Isolado)
Funções 4.x .NET 6.0 .NET 6.0
.NET 7.0 (GA)1
.NET Framework 4.8 (GA)1
Funções 1.x .NET Framework 4.8 n/a

1 O processo de compilação também requer o SDK .NET 6.

Para obter as últimas notícias sobre Funções do Azure lançamentos, incluindo a remoção de versões secundárias mais antigas específicas, monitorize Serviço de Aplicações do Azure anúncios.

Projeto de processo de trabalho isolado .NET

Um projeto de função isolada .NET é basicamente um projeto de aplicação de consola .NET que visa um runtime .NET suportado. Seguem-se os ficheiros básicos necessários em qualquer projeto isolado .NET:

Para obter exemplos completos, veja o projeto de exemplo isolado .NET 6 e o projeto de exemplo isolado .NET Framework 4,8.

Nota

Para poder publicar o projeto de função isolada numa aplicação de funções do Windows ou linux no Azure, tem de definir um valor de dotnet-isolated na definição da aplicação FUNCTIONS_WORKER_RUNTIME remota. Para suportar a implementação zip e a execução a partir do pacote de implementação no Linux, também tem de atualizar a definição de configuração do linuxFxVersion site para DOTNET-ISOLATED|7.0. Para saber mais, veja Atualizações manuais de versões no Linux.

Referências de pacotes

Um projeto de processo de trabalho isolado das Funções .NET utiliza um conjunto exclusivo de pacotes, para funcionalidades principais e extensões de enlace.

Pacotes principais

São necessários os seguintes pacotes para executar as funções .NET num processo de trabalho isolado:

Pacotes de extensões

Uma vez que as funções de processo de trabalho isolado .NET utilizam diferentes tipos de enlace, necessitam de um conjunto exclusivo de pacotes de extensão de enlace.

Encontrará estes pacotes de extensões em Microsoft.Azure.Functions.Worker.Extensions.

Configuração e arranque

Ao utilizar funções isoladas de .NET, tem acesso ao arranque da sua aplicação de funções, que normalmente se encontra em Program.cs. É responsável por criar e iniciar a sua própria instância de anfitrião. Como tal, também tem acesso direto ao pipeline de configuração da sua aplicação. Com o processo de trabalho isolado das Funções .NET, pode adicionar configurações, injetar dependências muito mais facilmente e executar o seu próprio middleware.

O código seguinte mostra um exemplo de um pipeline HostBuilder :

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults(builder =>
    {
        builder
            .AddApplicationInsights()
            .AddApplicationInsightsLogger();
    })
    .ConfigureServices(s =>
    {
        s.AddSingleton<IHttpResponderService, DefaultHttpResponderService>();
    })
    .Build();

Este código requer using Microsoft.Extensions.DependencyInjection;.

Um HostBuilder é utilizado para criar e devolver uma instância IHost totalmente inicializada, que é executada de forma assíncrona para iniciar a sua aplicação de funções.

await host.RunAsync();

Importante

Se o projeto tiver como destino .NET Framework 4.8, também terá de adicionar FunctionsDebugger.Enable(); antes de criar o HostBuilder. Deve ser a primeira linha do seu Main() método. Para obter mais informações, veja Depuração ao direcionar .NET Framework.

Configuração

O método ConfigureFunctionsWorkerDefaults é utilizado para adicionar as definições necessárias para que a aplicação de funções seja executada num processo de trabalho isolado, que inclui a seguinte funcionalidade:

  • Conjunto predefinido de conversores.
  • Defina jsonSerializerOptions predefinido para ignorar o invólucro nos nomes de propriedades.
  • Integrar com o registo de Funções do Azure.
  • Middleware de enlace de saída e funcionalidades.
  • Middleware de execução de funções.
  • Suporte gRPC predefinido.
.ConfigureFunctionsWorkerDefaults(builder =>
{
    builder
        .AddApplicationInsights()
        .AddApplicationInsightsLogger();
})

Ter acesso ao pipeline do construtor de anfitriões significa que também pode definir configurações específicas da aplicação durante a inicialização. Pode chamar o método ConfigureAppConfiguration no HostBuilder uma ou mais vezes para adicionar as configurações necessárias para a sua aplicação de funções. Para saber mais sobre a configuração da aplicação, veja Configuração no ASP.NET Core.

Estas configurações aplicam-se à sua aplicação de funções em execução num processo separado. Para fazer alterações ao anfitrião de funções ou configuração de acionador e enlace, ainda terá de utilizar o ficheiro host.json.

Injeção de dependência

A injeção de dependências é simplificada em comparação com as bibliotecas de classes .NET. Em vez de ter de criar uma classe de arranque para registar serviços, basta chamar ConfigureServices no construtor de anfitriões e utilizar os métodos de extensão em IServiceCollection para injetar serviços específicos.

O exemplo seguinte injeta uma dependência de serviço singleton:

.ConfigureServices(s =>
{
    s.AddSingleton<IHttpResponderService, DefaultHttpResponderService>();
})

Este código requer using Microsoft.Extensions.DependencyInjection;. Para saber mais, veja Injeção de dependência no ASP.NET Core.

Middleware

O .NET isolado também suporta o registo de middleware, novamente com um modelo semelhante ao que existe no ASP.NET. Este modelo permite-lhe injetar lógica no pipeline de invocação e antes e depois da execução das funções.

O método de extensão ConfigureFunctionsWorkerDefaults tem uma sobrecarga que lhe permite registar o seu próprio middleware, como pode ver no exemplo seguinte.

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();

O UseWhen método de extensão pode ser utilizado para registar um middleware que é executado condicionalmente. Tem de passar para este método um predicado que devolve um valor booleano e o middleware participa no pipeline de processamento de invocação quando o valor devolvido do predicado é true.

Os seguintes métodos de extensão no FunctionContext facilitam o trabalho com middleware no modelo isolado.

Método Descrição
GetHttpRequestDataAsync Obtém a HttpRequestData instância quando é chamada por um acionador HTTP. Este método devolve uma instância de ValueTask<HttpRequestData?>, que é útil quando quer ler dados de mensagens, como cabeçalhos de pedido e cookies.
GetHttpResponseData Obtém a HttpResponseData instância quando é chamada por um acionador HTTP.
GetInvocationResult Obtém uma instância de InvocationResult, que representa o resultado da execução da função atual. Utilize a Value propriedade para obter ou definir o valor conforme necessário.
GetOutputBindings Obtém as entradas de enlace de saída para a execução da função atual. Cada entrada no resultado deste método é do tipo OutputBindingData. Pode utilizar a Value propriedade para obter ou definir o valor conforme necessário.
BindInputAsync Vincula um item de enlace de entrada para a instância pedida BindingMetadata . Por exemplo, pode utilizar este método quando tem uma função com um BlobInput enlace de entrada que precisa de ser acedido ou atualizado pelo middleware.

Segue-se um exemplo de uma implementação de middleware que lê a HttpRequestData instância e atualiza a instância durante a HttpResponseData execução da função. Este middleware verifica a presença de um cabeçalho de pedido específico (x-correlationId) e, quando presente, utiliza o valor do cabeçalho para carimbar um cabeçalho de resposta. Caso contrário, gera um novo valor GUID e utiliza-o para carimbar o cabeçalho de resposta.

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);
    }
}

Para obter um exemplo mais completo da utilização do middleware personalizado na sua aplicação de funções, veja o exemplo de referência de middleware personalizado.

Tokens de cancelamento

Uma função pode aceitar um parâmetro CancellationToken , que permite ao sistema operativo notificar o código quando a função está prestes a ser terminada. Pode utilizar esta notificação para se certificar de que a função não termina inesperadamente de uma forma que deixe os dados num estado inconsistente.

Os tokens de cancelamento são suportados nas funções .NET quando são executados num processo de trabalho isolado. O exemplo seguinte gera uma exceção quando um pedido de cancelamento é recebido:

[Function(nameof(ThrowOnCancellation))]
public async Task ThrowOnCancellation(
    [EventHubTrigger("sample-workitem-1", Connection = "EventHubConnection")] string[] messages,
    FunctionContext context,
    CancellationToken cancellationToken)
{
    _logger.LogInformation("C# EventHub {functionName} trigger function processing a request.", nameof(ThrowOnCancellation));

    foreach (var message in messages)
    {
        cancellationToken.ThrowIfCancellationRequested();
        await Task.Delay(6000); // task delay to simulate message processing
        _logger.LogInformation("Message '{msg}' was processed.", message);
    }
}

O exemplo seguinte executa ações de limpeza se tiver sido recebido um pedido de cancelamento:

[Function(nameof(HandleCancellationCleanup))]
public async Task HandleCancellationCleanup(
    [EventHubTrigger("sample-workitem-2", Connection = "EventHubConnection")] string[] messages,
    FunctionContext context,
    CancellationToken cancellationToken)
{
    _logger.LogInformation("C# EventHub {functionName} trigger function processing a request.", nameof(HandleCancellationCleanup));

    foreach (var message in messages)
    {
        if (cancellationToken.IsCancellationRequested)
        {
            _logger.LogInformation("A cancellation token was received, taking precautionary actions.");
            // Take precautions like noting how far along you are with processing the batch
            _logger.LogInformation("Precautionary activities complete.");
            break;
        }

        await Task.Delay(6000); // task delay to simulate message processing
        _logger.LogInformation("Message '{msg}' was processed.", message);
    }
}

ReadyToRun

Pode compilar a sua aplicação de funções como binários ReadyToRun. ReadyToRun é uma forma de compilação antecipada que pode melhorar o desempenho do arranque para ajudar a reduzir o efeito do arranque a frio ao executar num plano de Consumo.

ReadyToRun está disponível no .NET 6 e versões posteriores e requer a versão 4.0 ou posterior do runtime Funções do Azure.

Para compilar o projeto como ReadyToRun, atualize o ficheiro do projeto ao adicionar os <PublishReadyToRun> elementos e <RuntimeIdentifier> . Segue-se a configuração para publicar numa aplicação de funções do Windows de 32 bits.

<PropertyGroup>
  <TargetFramework>net6.0</TargetFramework>
  <AzureFunctionsVersion>v4</AzureFunctionsVersion>
  <RuntimeIdentifier>win-x86</RuntimeIdentifier>
  <PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>

Contexto de execução

O .NET isolado transmite um objeto FunctionContext aos métodos de função. Este objeto permite-lhe obter uma instância do ILogger para escrever nos registos ao chamar o método GetLogger e ao fornecer uma categoryName cadeia. Para saber mais, veja Registo.

Enlaces

Os enlaces são definidos através da utilização de atributos em métodos, parâmetros e tipos de retorno. Um método de função é um método com um Function atributo e um atributo de acionador aplicados a um parâmetro de entrada, conforme mostrado no exemplo seguinte:

[Function("QueueFunction")]
[QueueOutput("output-queue")]
public static string[] Run([QueueTrigger("input-queue")] Book myQueueItem, FunctionContext context)

O atributo de acionador especifica o tipo de acionador e vincula os dados de entrada a um parâmetro de método. A função de exemplo anterior é acionada por uma mensagem de fila e a mensagem de fila é transmitida para o método no myQueueItem parâmetro .

O Function atributo marca o método como um ponto de entrada de função. O nome tem de ser exclusivo num projeto, começar com uma letra e conter apenas letras, números, _e -, até 127 carateres de comprimento. Os modelos de projeto criam frequentemente um método chamado Run, mas o nome do método pode ser qualquer nome de método C# válido.

Os enlaces podem fornecer dados como cadeias, matrizes e tipos serializáveis, como objetos de classe antiga simples (POCOs). Também pode vincular a tipos de alguns SDKs de serviço.

Para acionadores HTTP, tem de utilizar HttpRequestData e HttpResponseData para aceder aos dados de pedido e resposta. Isto deve-se ao facto de não ter acesso aos objetos de pedido e resposta HTTP originais ao utilizar o processo de trabalho isolado das Funções .NET.

Para obter um conjunto completo de exemplos de referência para utilizar acionadores e enlaces com funções de processo de trabalho isoladas, veja o exemplo de referência de extensões de enlace.

Enlaces de entrada

Uma função pode ter zero ou mais enlaces de entrada que podem transmitir dados para uma função. Tal como os acionadores, os enlaces de entrada são definidos ao aplicar um atributo de enlace a um parâmetro de entrada. Quando a função é executada, o runtime tenta obter dados especificados no enlace. Os dados pedidos dependem frequentemente das informações fornecidas pelo acionador através de parâmetros de enlace.

Enlaces de saída

Para escrever num enlace de saída, tem de aplicar um atributo de enlace de saída ao método de função, que definiu como escrever no serviço vinculado. O valor devolvido pelo método é escrito no enlace de saída. Por exemplo, o exemplo seguinte escreve um valor de cadeia numa fila de mensagens com o nome output-queue com um enlace de saída:

[Function("QueueFunction")]
[QueueOutput("output-queue")]
public static string[] Run([QueueTrigger("input-queue")] Book myQueueItem, FunctionContext context)
{
    // 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;
}

Vários enlaces de saída

Os dados escritos num enlace de saída são sempre o valor devolvido da função. Se precisar de escrever em mais do que um enlace de saída, tem de criar um tipo de retorno personalizado. Este tipo de retorno tem de ter o atributo de enlace de saída aplicado a uma ou mais propriedades da classe. O exemplo seguinte de um acionador HTTP escreve na resposta HTTP e num enlace de saída de fila:

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; }
}

A resposta de um acionador HTTP é sempre considerada uma saída, pelo que não é necessário um atributo de valor devolvido.

Tipos de SDK (pré-visualização)

Para alguns tipos de enlace específicos do serviço, os dados de enlace podem ser fornecidos através de tipos de arquiteturas e SDKs de serviço. Estes fornecem uma capacidade adicional para além do que uma cadeia serializada ou um objeto CLR (POCO) simples pode oferecer. O suporte para tipos de SDK está atualmente em pré-visualização com cobertura de cenário limitada.

Para utilizar enlaces de tipo SDK, o projeto tem de referenciar Microsoft.Azure.Functions.Worker 1.12.1-preview1 ou posterior e Microsoft.Azure.Functions.Worker.Sdk 1.9.0-preview1 ou posterior. Também serão necessárias versões de pacotes específicas para cada uma das extensões de serviço. Ao testar tipos de SDK localmente no seu computador, também terá de utilizar Funções do Azure versão 4.0.5000 ou posterior do Core Tools. Pode verificar a sua versão atual com o comando func version.

Os seguintes enlaces específicos do serviço estão atualmente incluídos na pré-visualização:

Serviço Acionador Enlace de entrada Enlace de saída
Blobs do Azure Suporte de pré-visualização Suporte de pré-visualização Ainda não suportado

Os exemplos de enlace do tipo SDK mostram exemplos de trabalho com os vários tipos suportados.

Nota

Ao utilizar expressões de enlace que dependem de dados de acionador, os tipos de SDK para o próprio acionador não são suportados.

Acionador HTTP

Os acionadores HTTP traduzem a mensagem de pedido HTTP recebida num objeto HttpRequestData que é transmitido para a função. Este objeto fornece dados do pedido, incluindo Headers, Cookies, Identities, URLe opcional uma mensagem Body. Este objeto é uma representação do objeto de pedido HTTP e não do próprio pedido.

Da mesma forma, a função devolve um objeto HttpResponseData , que fornece dados utilizados para criar a resposta HTTP, incluindo a mensagem StatusCode, Headerse, opcionalmente, uma mensagem Body.

O código seguinte é um acionador HTTP

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

    var response = req.CreateResponse(HttpStatusCode.OK);
   
    response.Headers.Add("Content-Type", "text/plain; charset=utf-8");

    response.WriteString("Welcome to .NET isolated worker !!");

    return response;
}

Registo

No .NET isolado, pode escrever nos registos com uma instância do ILogger obtida a partir de um objeto FunctionContext transmitido para a sua função. Chame o método GetLogger , transmitindo um valor de cadeia que é o nome da categoria na qual os registos são escritos. Normalmente, a categoria é o nome da função específica a partir da qual os registos são escritos. Para saber mais sobre categorias, veja o artigo de monitorização.

O exemplo seguinte mostra como obter um ILogger e escrever registos numa função:

var logger = executionContext.GetLogger("HttpFunction");
logger.LogInformation("message logged");

Utilize vários métodos do ILogger para escrever vários níveis de registo, como LogWarning ou LogError. Para saber mais sobre os níveis de registo, veja o artigo de monitorização.

Também é fornecido um ILogger ao utilizar a injeção de dependências.

Depuração ao filtrar .NET Framework

Se o projeto isolado for .NET Framework 4.8, o âmbito de pré-visualização atual requer passos manuais para ativar a depuração. Estes passos não são necessários se utilizar outra arquitetura de destino.

A sua aplicação deve começar com uma chamada para FunctionsDebugger.Enable(); como primeira operação. Isto ocorre no Main() método antes de inicializar um HostBuilder. O seu Program.cs ficheiro deve ter um aspeto semelhante ao seguinte:

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();
        }
    }
}

Em seguida, tem de anexar manualmente ao processo com um depurador de .NET Framework. O Visual Studio ainda não o faz automaticamente para o processo de trabalho isolado .NET Framework aplicações e a operação "Iniciar Depuração" deve ser evitada.

No diretório do projeto (ou no respetivo diretório de saída de compilação), execute:

func host start --dotnet-isolated-debug

Esta ação irá iniciar a sua função de trabalho e o processo irá parar com a seguinte mensagem:

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

Onde <process id> está o ID do processo de trabalho. Agora pode utilizar o Visual Studio para anexar manualmente ao processo. Para obter instruções sobre esta operação, veja Como anexar a um processo em execução.

Depois de anexar o depurador, a execução do processo é retomada e poderá depurar.

Depuração Remota com o Visual Studio

Uma vez que a aplicação de processo de trabalho isolado é executada fora do runtime das Funções, tem de anexar o depurador remoto a um processo separado. Para saber mais sobre a depuração com o Visual Studio, veja Depuração Remota.

Passos seguintes