Async/Await pode estar te prejudicando em performance
Este artigo é baseado em alguns estudos que estou realizando e na busca de manter esse conhecimento, decidi escreve-lo para compartilhar o que tenho aprendendo.
Se você é um dev .Net com certeza já utilizou ou está utilizando métodos async/await. Vamos ver que mesmo algo que foi pensado para nos auxiliar em performance, se utilizado de “forma errada”, pode prejudicar a performance.
Vou mostrar que podemos ganhar em performance utilizando o async/await. Claro, sempre vai depender de onde você vai utilizar. Por exemplo, caso você precisa fazer uma consulta no banco, você pode chamar o método do seu “BaseRepository” sem utilizar o “await”. Vou mostrar isso em seguida. Para estes testes, estou utilizando o .NET 8.0.10 (8.0.1024.46610), X64.
Aqui utilizei o site SharpLab para pegar o código gerado.
Show me the code!
Nossa classe de teste. Nossa classe está muito simples, é apenas para exibir a diferença em chamar o método “corretamente”.
Nossa classe onde executamos nosso teste.
Resultado do nosso teste.
Como podemos ver, sem o “await” dentro do nosso método (ReturnWithoutAwaitAsync) foi aproximadamente 6 vezes mais rápido. Mas agora, vamos ver esses métodos “por dentro”.
Vou resumir aqui o que acontece com o “async/await”, mas pretendo fazer um novo artigo com mais detalhes sobre isso. Mas quando criamos um método “async/await”, criamos uma máquina de estado. Nessa máquina de estado, temos algumas informações do estado do “await”, se já foi executado, se será a primeira vez, etc e, temos uma “cópia” do método a ser executado. Veja a quantidade de código que foi criado “a partir do await”.
[CompilerGenerated] privatesealed class <M>d__1 : IAsyncStateMachine { public int <>1__state; public AsyncTaskMethodBuilder <>t__builder; [Nullable(0)] public C <>4__this; [Nullable(0)] private string <message>5__1; [Nullable(0)] private string <>s__2; [Nullable(newbyte[] {0,1 })] private TaskAwaiter<string> <>u__1; privatevoidMoveNext() { // Copia o estado atual para a variável local // Valor inicial será -1 intnum=<>1__state; try { // Variável que irá manter o retorno da nova task TaskAwaiter<string>awaiter; if (num!=0) { awaiter=<>4__this.Print().GetAwaiter(); if (!awaiter.IsCompleted) { num= (<>1__state=0); <>u__1= awaiter; <M>d__1stateMachine=this; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter,ref stateMachine); // Primeira chamada: Libera a CPU ou Thread para quem chamou o método return; } } else { // Chamada de "despertar": Aque pegamos o "awaiter" do contexto de execução awaiter=<>u__1; <>u__1=default(TaskAwaiter<string>); num= (<>1__state=-1); } <>s__2= awaiter.GetResult(); <message>5__1=<>s__2; <>s__2=null; Console.WriteLine(<message>5__1); } catch (Exceptionexception) { <>1__state=-2; <message>5__1=null; <>t__builder.SetException(exception); return; } <>1__state=-2; <message>5__1=null; <>t__builder.SetResult(); } voidIAsyncStateMachine.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext this.MoveNext(); } [DebuggerHidden] privatevoidSetStateMachine(IAsyncStateMachinestateMachine) { } voidIAsyncStateMachine.SetStateMachine(IAsyncStateMachinestateMachine) { //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine this.SetStateMachine(stateMachine); } }
Vamos ver como fica um método onde chamamos o await dentro dele. Aqui mostro o mesmo método que utilizei no teste, veja que ele acabou criando 2 máquinas de estado.
Resultado do código acima:
[CompilerGenerated] privatesealed class <M>d__2 : IAsyncStateMachine { public int <>1__state; public AsyncTaskMethodBuilder <>t__builder; [Nullable(0)] public C <>4__this; [Nullable(0)] private string <message>5__1; [Nullable(0)] private string <>s__2; [Nullable(newbyte[] {0,1 })] private TaskAwaiter<string> <>u__1; privatevoidMoveNext() { intnum=<>1__state; try { TaskAwaiter<string>awaiter; if (num!=0) { awaiter=<>4__this.MyMethodWithAwaitAsync().GetAwaiter(); if (!awaiter.IsCompleted) { num= (<>1__state=0); <>u__1= awaiter; <M>d__2stateMachine=this; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter,ref stateMachine); return; } } else { awaiter=<>u__1; <>u__1=default(TaskAwaiter<string>); num= (<>1__state=-1); } <>s__2= awaiter.GetResult(); <message>5__1=<>s__2; <>s__2=null; } catch (Exceptionexception) { <>1__state=-2; <message>5__1=null; <>t__builder.SetException(exception); return; } <>1__state=-2; <message>5__1=null; <>t__builder.SetResult(); } voidIAsyncStateMachine.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext this.MoveNext(); } [DebuggerHidden] privatevoidSetStateMachine(IAsyncStateMachinestateMachine) { } voidIAsyncStateMachine.SetStateMachine(IAsyncStateMachinestateMachine) { //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine this.SetStateMachine(stateMachine); } } [CompilerGenerated] privatesealed class <MyMethodWithAwaitAsync>d__1 : IAsyncStateMachine { public int <>1__state; [Nullable(0)] public AsyncTaskMethodBuilder<string> <>t__builder; [Nullable(0)] public C <>4__this; [Nullable(0)] private string <>s__1; [Nullable(0)] private TaskAwaiter<string> <>u__1; privatevoidMoveNext() { intnum=<>1__state; stringresult; try { TaskAwaiter<string>awaiter; if (num!=0) { awaiter= Task.FromResult("Hello World!").GetAwaiter(); if (!awaiter.IsCompleted) { num= (<>1__state=0); <>u__1= awaiter; <MyMethodWithAwaitAsync>d__1stateMachine=this; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter,ref stateMachine); return; } } else { awaiter=<>u__1; <>u__1=default(TaskAwaiter<string>); num= (<>1__state=-1); } <>s__1= awaiter.GetResult(); result=<>s__1; } catch (Exceptionexception) { <>1__state=-2; <>t__builder.SetException(exception); return; } <>1__state=-2; <>t__builder.SetResult(result); } voidIAsyncStateMachine.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext this.MoveNext(); } [DebuggerHidden] privatevoidSetStateMachine(IAsyncStateMachinestateMachine) { } voidIAsyncStateMachine.SetStateMachine(IAsyncStateMachinestateMachine) { //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine this.SetStateMachine(stateMachine); } }
Agora vamos ver sem o await interno.
Resultado do código acima:
[CompilerGenerated] privatesealed class <M>d__2 : IAsyncStateMachine { public int <>1__state; public AsyncTaskMethodBuilder <>t__builder; [Nullable(0)] public C <>4__this; [Nullable(0)] private string <message>5__1; [Nullable(0)] private string <>s__2; [Nullable(newbyte[] {0,1 })] private TaskAwaiter<string> <>u__1; privatevoidMoveNext() { intnum=<>1__state; try { TaskAwaiter<string>awaiter; if (num!=0) { awaiter=<>4__this.MyMethodWithAwaitAsync().GetAwaiter(); if (!awaiter.IsCompleted) { num= (<>1__state=0); <>u__1= awaiter; <M>d__2stateMachine=this; <>t__builder.AwaitUnsafeOnCompleted(ref awaiter,ref stateMachine); return; } } else { awaiter=<>u__1; <>u__1=default(TaskAwaiter<string>); num= (<>1__state=-1); } <>s__2= awaiter.GetResult(); <message>5__1=<>s__2; <>s__2=null; } catch (Exceptionexception) { <>1__state=-2; <message>5__1=null; <>t__builder.SetException(exception); return; } <>1__state=-2; <message>5__1=null; <>t__builder.SetResult(); } voidIAsyncStateMachine.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext this.MoveNext(); } [DebuggerHidden] privatevoidSetStateMachine(IAsyncStateMachinestateMachine) { } voidIAsyncStateMachine.SetStateMachine(IAsyncStateMachinestateMachine) { //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine this.SetStateMachine(stateMachine); } }
Está muito mais simples. Criou apenas uma máquina de estado.
Apenas para comparação e curiosidade, com o await interno, temos 197 linhas. Sem o await interno, temos 116 linhas.
Conclusão
Utilize sim o async/await que você terá performance, principalmente em escalabilidade. Claro que nem em todo método será possível utilizar a “técnica” apresentada aqui.
Pretendo em um próximo artigo, trazer um pouco mais de detalhes sobre a máquina de estado que o asyn/await cria.
Obrigado pela sua leitura.
Share this content:
Deixe uma resposta