O que muda no runtime do .NET 11 Preview 7
O .NET 11 Preview 7 concentra uma quantidade incomum de trabalho na camada mais baixa da plataforma, ou seja, no runtime, no compilador JIT, no NativeAOT e na infraestrutura de diagnóstico. A Microsoft publicou essa preview em agosto de 2026, ainda no caminho para a versão final prevista para novembro de 2026.
Não se trata de novas APIs, porque quase tudo aqui afeta o custo de execução de um código que você já escreveu. Por isso, este artigo detalha o que mudou e qual o impacto prático para quem escreve C#.
O que o .NET 11 Preview 7 traz para o runtime
O anúncio oficial agrupa as novidades do runtime em quatro frentes. Primeiro, o runtime async ganha compilação em camadas e otimizações de tail-await.
Em seguida, o CoreCLR em WebAssembly passa a executar a suíte de testes das bibliotecas. Além disso, o JIT recebe uma série de melhorias de geração de código, e o NativeAOT fica mais rápido em despacho e em tempo de compilação.
Se você acompanhou as previews anteriores, vale comparar com as novidades do runtime no .NET 11 Preview 5, que prepararam boa parte do terreno. Também vale ver o que mudou em paralelo no ASP.NET Core desta mesma preview.
Por que o runtime async muda o jogo
O .NET 11 traz uma mudança estrutural que vinha sendo preparada há alguns ciclos, o runtime-native async, também chamado de Runtime Async.
Historicamente, o compilador C# transformava cada método async em uma máquina de estados. Essa estrutura incluía uma classe auxiliar, campos para cada variável local capturada e um método MoveNext que reentrava no ponto de suspensão.
No modelo novo, essa responsabilidade sai do compilador e vai para o runtime. Dessa forma, o próprio runtime gerencia a suspensão e a retomada dos métodos.
A vantagem mais visível aparece nos stack traces ao vivo. Segundo a documentação oficial do runtime do .NET 11, o modelo antigo produz vários frames de infraestrutura, enquanto o Runtime Async mostra apenas os métodos reais na pilha.
No exemplo da documentação, uma cadeia de três métodos assíncronos cai de 13 frames para 5.
Vale registrar outro detalhe importante. No .NET 11, as bibliotecas do runtime já são compiladas com runtime-async=on e não contêm mais máquinas de estado geradas pelo compilador.
Em outras palavras, qualquer aplicação que consome a BCL já exercita esse caminho. Por isso, o .NET 11 Preview 7 não expande a funcionalidade, mas ataca a performance do modelo novo até superar o antigo.
Compilação em camadas para métodos async no .NET 11 Preview 7
A mudança mais relevante do .NET 11 Preview 7 é conceitualmente simples. As versões assíncronas dos métodos agora passam pelo pipeline de tiered compilation.
Até então, esses métodos ignoravam o mecanismo de camadas e executavam para sempre o código de tier0. Contudo, o tier0 é otimizado para velocidade de compilação, e não para throughput em regime estável.
No tiered compilation, o runtime gera código rápido com o quick JIT ou via ReadyToRun e, depois, recompila os métodos quentes de forma otimizada. Ficar preso no tier0, portanto, significa abrir mão de inline agressivo, de eliminação de alocações e do PGO dinâmico.
Na prática, isso aparecia em produção como uma taxa de alocação alta até o aquecimento da aplicação.
Além de destravar o tier1, duas mudanças no próprio tier0 tornam a transição mais barata.
Tail-await também no tier0
A primeira mudança faz a otimização de tail-await rodar também no tier0. Assim, o código ainda frio já aloca bem menos.
No benchmark platform-json do TechEmpower, a taxa máxima de alocação durante o warmup caiu de cerca de 110,5 MB/s para cerca de 8 MB/s. Ou seja, a redução foi de aproximadamente 93%.
Inline de AsyncHelpers.TransparentAwait
A segunda mudança permite que o JIT faça inline de AsyncHelpers.TransparentAwait. Com isso, um await quente sobre uma Task já completada deixa de chamar um helper e vira apenas uma verificação nos flags de status da tarefa.
O número impressiona. Segundo as notas de release do .NET 11 Preview 7, um laço que aguarda uma Task já completada cem milhões de vezes caiu de aproximadamente 191 ms para aproximadamente 32 ms.
Esse padrão é comum, pois qualquer método que retorna um cache quente ou um resultado memoizado cai nesse caso.
Intrínsecos de fábricas de Task e ValueTask
O JIT passa a reconhecer os métodos de fábrica mais comuns de Task e ValueTask dentro de uma versão assíncrona. Então, ele dobra essas chamadas em caminhos rápidos diretos.
A cobertura inclui Task.FromResult, Task.CompletedTask, ValueTask.FromResult, ValueTask.CompletedTask, default(ValueTask), new ValueTask() e new ValueTask<T>(T). Além disso, o JIT reconhece e desembrulha os wrappers de adaptação entre Task e ValueTask.
O exemplo abaixo, retirado das notas de release, ilustra o padrão:
[MethodImpl(MethodImplOptions.NoInlining)]
private static ValueTask TestTaskToValueTask() => new ValueTask(TestTask());
[MethodImpl(MethodImplOptions.NoInlining)]
private static async Task TestTask() => await Task.Yield();
Antes, o new ValueTask(...) gerava 115 bytes, com frame completo, chamada ao construtor de ValueTask e uma chamada ao helper TransparentAwaitWithResult.
Agora, o mesmo trecho gera 29 bytes, com uma tail call direta para TestTask() seguida de uma verificação de status. Isso representa uma redução de aproximadamente 75% no tamanho do código.
Esse tipo de adaptação é comum em bibliotecas que expõem ValueTask e delegam para implementações internas com Task.
Tailcalls implícitas e o caso do Task.Yield
As tailcalls implícitas voltam a funcionar a partir de métodos assíncronos. Isso vale quando a tarefa retornada pelo método chamado é exatamente a que o método atual retornaria.
Um método que apenas despacha entre dois métodos async Task, no formato return b ? Bar() : Baz();, caiu de 52 bytes e 19 instruções para 20 bytes e 6 instruções. Nesse caso, os dois ramos viram tail.jmp.
Como pipelines de middleware e decorators dependem desse padrão, o ganho se acumula a cada camada.
O await Task.Yield() em métodos runtime-async também deixa de alocar o box interno do thread pool. O modelo de máquina de estados já fazia isso através de IStateMachineBoxAwareAwaiter.
Um benchmark com dez milhões de iterações caiu de aproximadamente 723 ms para aproximadamente 534 ms. Com isso, o runtime async empata com o modelo gerado pelo compilador.
Por fim, a equipe corrigiu um bug que impedia o await de salvar e restaurar contextos assíncronos em métodos que retornam ValueTask. Essa correção importa, pois o ExecutionContext carrega estado como AsyncLocal<T>.
CoreCLR no WebAssembly com o .NET 11 Preview 7
O .NET 11 está levando o CoreCLR para o WebAssembly com um modelo portável de interpretador combinado com ReadyToRun. Assim, o RyuJIT gera o código ahead-of-time.
No Preview 6, essa configuração apenas conseguia iniciar. Já no .NET 11 Preview 7, ela executa a suíte de testes das bibliotecas de ponta a ponta, o que marca um salto de maturidade.
Para chegar até aí, o tratamento de exceções migrou para a proposta padronizada exnref, com try_table e throw_ref. Essa migração atravessou o Emscripten, o wasm-opt e o jiterpreter.
O RyuJIT também ganhou SIMD para a maior parte dos intrínsecos de PackedSimd e P/Invoke inline. Além disso, um corehost de CoreCLR para WASI, chamado wasihost, viabilizou a execução dos testes.
O cDAC também aprendeu a percorrer a pilha em WebAssembly, e o Emscripten subiu para a versão 6.0.2, com o runtime de browser religado com -O3. Além disso, o WebCIL V1 vira o padrão para builds CoreCLR em WASM.
O mesmo movimento de consolidação do CoreCLR acontece no mobile, como mostra a migração do .NET MAUI para o CoreCLR no .NET 11.
JIT e geração de código no .NET 11 Preview 7
O bloco de otimizações do JIT no .NET 11 Preview 7 é amplo. Por isso, vale percorrê-lo por categoria.
Intrínsecos AVX-VNNI-512
O tipo System.Runtime.Intrinsics.X86.AvxVnni.V512 expõe as formas AVX-512 das instruções de multiplicação e acumulação do AVX-VNNI.
Para isso, o JIT considera o CPUID AVX512-VNNI e emite as codificações EVEX-512 de VPDPBUSD, VPDPBUSDS, VPDPWSSD e VPDPWSSDS.
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
if (AvxVnni.V512.IsSupported)
{
Vector512<int> acc = Vector512<int>.Zero;
Vector512<byte> a = Vector512.Create((byte)1);
Vector512<sbyte> b = Vector512.Create((sbyte)2);
// Um único VPDPBUSD zmm, zmm, zmm
Vector512<int> result = AvxVnni.V512.MultiplyWideningAndAdd(acc, a, b);
}
Quem mais se beneficia dessa API trabalha com quantização de modelos, inferência em CPU e processamento de sinais em precisão reduzida.
Arm64 e a extensão CSSC
Em hardware Armv8.9 ou Armv9.4 com FEAT_CSSC, os métodos BitOperations.PopCount e BitOperations.TrailingZeroCount compilam para uma única instrução escalar. Assim, eles deixam de trafegar pela unidade SIMD.
; PopCount(uint)
- movi v16.8b, #0
- ins v16.s[0], w0
- cnt v16.8b, v16.8b
- addv b16, v16.8b
- umov w0, v16.s[0]
+ cnt w0, w0
A sequência de cinco instruções vira uma. Em bitsets, índices invertidos e filtros de Bloom, que dependem de contagem de bits, isso aparece no perfil.
Coalescência de stores em endereços constantes
O passe de coalescência de stores do JIT agora reconhece nós STOREIND cujo endereço é uma constante absoluta não relocável.
Assim, escritas adjacentes em campos de structs estáticos sem referências gerenciadas viram um único store. Na prática, dois mov dword se transformam em um mov qword.
Da mesma forma, a atribuição de um campo Int128 vira um único vmovups de 16 bytes.
Devirtualização de Activator.CreateInstance
O JIT passa a saber que Activator.CreateInstance<T>() sempre produz exatamente T. Isso também vale para a chamada que o compilador emite para a restrição where T : new().
Com isso, o JIT consegue devirtualizar as chamadas virtuais feitas logo em seguida sobre a instância recém-criada.
static T Get<T>() where T : new() => new T();
class A { public virtual int Value => 1; }
class B : A { public override int Value => 2; }
Console.WriteLine(Get<A>().Value); // Devirtualizado para A.get_Value
Console.WriteLine(Get<B>().Value); // Devirtualizado para B.get_Value
Esse padrão é comum em factories genéricas, builders e código de serialização.
A devirtualização também passa a cobrir métodos virtuais genéricos compartilhados e métodos de interface genéricos com implementação padrão.
Conversões saturadas e outros ajustes
Inserções de um escalar extraído de outro vetor agora dobram em um único insertps.
As otimizações de laço também propagam fatos simples de não negatividade sobre variáveis de indução. Por exemplo, um if (i >= 0) dentro de um for que começa em zero desaparece.
As conversões não verificadas de float e double para sbyte, byte, short, ushort e char agora saturam nos limites do tipo de destino.
Essa última mudança corrige um resultado, e não apenas a performance. Antes, (short)(double)32768.000000000007 produzia -32768, porque o JIT expandia a conversão como R → int → smallT e só a etapa interna saturava.
A correção cobre todas as arquiteturas suportadas, o interpretador do CoreCLR e o pré-inicializador de tipos do NativeAOT.
O JIT também passou a eliminar os boxes de Enum.HasFlag já no tier0.
Por fim, o limite de 128 bits para Vector<T> em Arm64 com SVE caiu. Assim, o tipo reporta sua largura real até o comprimento vetorial do hardware, em linha com a passagem por referência que chegou no Preview 6.
NativeAOT no .NET 11 Preview 7
No .NET 11 Preview 7, o despacho de métodos virtuais genéricos usa a mesma infraestrutura de células de despacho do despacho de interfaces.
| Tipo de callsite | Antes | Depois |
|---|---|---|
| GVM monomórfico | 2,45 ns por chamada | 1,74 ns por chamada |
| GVM polimórfico | 2,45 ns por chamada | 2,10 ns por chamada |
A redução fica em aproximadamente 29% no caso monomórfico e 14% no polimórfico.
O compilador ILC também passou a cachear enumerações e resoluções de slot de métodos virtuais. Com isso, a compilação de um dotnet new webapiaot caiu de 7,4 para 6,2 segundos, cerca de 16% mais rápida.
O método Assembly.GetCallingAssembly() agora funciona sob Native AOT, porque percorre os dados de stack trace para localizar o assembly chamador.
A equipe também concluiu o PAC-RET para Arm64 no NativeAOT, que assina e autentica endereços de retorno contra ataques de ROP.
Diagnóstico, breaking change e correções
O profiler de async agora instrumenta também o caminho de máquina de estados gerada pelo compilador, além do caminho runtime-async.
Assim, as ferramentas recebem um fluxo de eventos uniforme, qualquer que seja o modelo que produziu o código.
Além disso, o interpretador do CoreCLR registra suas faixas de IR no perf map com DOTNET_PerfMapEnabled=1 e DOTNET_PerfMapStubGranularity=2.
Há, contudo, um breaking change. O Assembly.GetCallingAssembly no Native AOT agora lança exceção quando os dados de stack trace não estão disponíveis.
Antes, o método sempre lançava nessa plataforma, então a mudança é parcial. Ainda assim, aplicações publicadas com StackTraceSupport=false não conseguem usar essa API.
Entre as várias correções da preview, duas merecem atenção porque corrigem miscompilações. A primeira eliminava indevidamente o bounds check para índices Phi com mínimo negativo, enquanto a segunda reordenava efeitos colaterais ao dobrar um BlendVariable com máscara constante zero.
Requisitos mínimos de hardware no .NET 11
Tudo isso faz parte de uma mudança maior do ciclo. A linha de base para Apple, Linux e Windows subiu de x86-64-v1 para x86-64-v2.
Com isso, o hardware passa a garantir CX16, POPCNT, SSE3, SSSE3, SSE4.1 e SSE4.2, e os últimos chips abaixo dessa linha saíram de suporte por volta de 2013.
O alvo de ReadyToRun também subiu para x86-64-v3 no Windows e no Linux. Esse alvo inclui AVX, AVX2, BMI1, BMI2, F16C, FMA, LZCNT e MOVBE.
Em consequência, o .NET 11 falha ao executar em hardware antigo e exibe uma mensagem sobre a ausência de conjuntos de instruções de linha de base.
Como aproveitar o .NET 11 Preview 7 na prática
Se você mantém serviços assíncronos de alto throughput, meça o warmup, e não apenas o regime estável.
A maior parte dos ganhos do .NET 11 Preview 7 aparece entre o início do processo e o aquecimento completo do tier1. Justamente nessa janela acontecem deploys, escalonamento horizontal e reciclagem de instâncias.
Se você publica com Native AOT, revise o uso de Assembly.GetCallingAssembly e a configuração de StackTraceSupport antes de atualizar.
Se o seu código depende de conversões numéricas fora de faixa, faça um passe de testes.
Para quem trabalha com Blazor WebAssembly, o amadurecimento do CoreCLR em WASM é o sinal mais importante. Ainda assim, ele não é o padrão nesta versão.
Por fim, lembre que o .NET 11 Preview 7 também trouxe mudanças de linguagem e de acesso a dados. Para ter a visão completa, veja as novidades do C# nesta preview e o que mudou no EF Core 11.
Conclusão sobre o .NET 11 Preview 7
O .NET 11 Preview 7 é uma preview de fundação, e não de vitrine.
O tema central fica claro, porque o runtime async deixou de ser uma promessa arquitetural. Agora, ele compete de igual para igual com o modelo de máquina de estados e chega a superá-lo em vários benchmarks.
Além disso, o .NET 11 Preview 7 consolida o CoreCLR como runtime universal, avançando sobre o WebAssembly.
Como toda preview, nada disso é definitivo até a versão final em novembro de 2026. Ainda assim, a direção está estabelecida, então vale acompanhar com benchmarks próprios.
FAQ
O Runtime Async já é o padrão no .NET 11?
Ele não exige mais EnablePreviewFeatures em projetos net11.0, e as bibliotecas do runtime já usam runtime-async=on. Ainda assim, continua um recurso em preview, e você pode desativá-lo por projeto com <UseRuntimeAsync>false</UseRuntimeAsync>.
Preciso mudar meu código para aproveitar essas otimizações?
Não, praticamente todas as mudanças são transparentes ao recompilar para o .NET 11. As exceções são as novas APIs de intrínsecos, como AvxVnni.V512, que exigem uso explícito.
As variáveis DOTNET_RuntimeAsync e UNSUPPORTED_RuntimeAsync ainda funcionam?
Não, elas foram removidas. O controle agora acontece pela propriedade de projeto UseRuntimeAsync.
Meu servidor antigo vai continuar rodando o .NET 11?
Depende do processador, porque a linha de base subiu para x86-64-v2. Em hardware abaixo disso, o runtime falha na inicialização com uma mensagem explícita.
A redução de 93% na alocação durante o warmup vale para qualquer aplicação?
Não necessariamente, porque o número vem do benchmark platform-json do TechEmpower, que é extremo em concorrência. Aplicações reais tendem a ver ganhos proporcionais ao peso do caminho assíncrono no perfil.
O CoreCLR em WebAssembly já substitui o Mono no Blazor?
Ainda não. A execução da suíte de testes das bibliotecas é um marco de maturidade, mas não uma declaração de disponibilidade geral.
Essas mudanças alteram o comportamento observável do programa?
Na maioria dos casos, não. A exceção é a saturação em conversões de ponto flutuante para inteiros pequenos, que corrige um resultado antes incorreto.