Programação assíncrona com c sharp

718 views

Published on

Programação assíncrona com C#

1 Comment
2 Likes
Statistics
Notes
No Downloads
Views
Total views
718
On SlideShare
0
From Embeds
0
Number of Embeds
179
Actions
Shares
0
Downloads
11
Comments
1
Likes
2
Embeds 0
No embeds

No notes for slide
  • Nesta pizzaria, haviam cinco garçons e dez mesas
  • Sempre que havia um pedido em uma mesa, o garçom o anotava, ia até a cozinha, o entregava ao pizzaiolo e ficava lá, esperando a pizza assar
    Depois de pronta, o garçom saía da cozinha e entregava a pizza na mesa e por fim, voltava ao balcão para esperar novos pedidos
  • O gerente percebeu que se houvessem mais que cinco pedidos sendo preparados, os próximos clientes não conseguiam fazer seus pedidos e iam embora
    Como ele era muito esperto, resolveu contratar mais cinco garçons para resolver o problema
    Desta forma, havia um garçom para cada mesa
  • Como ele era muito esperto, resolveu contratar mais cinco garçons para resolver o problema
    Desta forma, havia um garçom para cada mesa
  • Mas devido a contratação dos novos garçons, o custo operacional da pizzaria subiu muito, gerando prejuízos no final do mês
    Para cobrir estes gastos, o gerente resolveu aumentar o preço das pizzas, o que espantou a clientela, que passou a frequentar a Pizzeria Veloce do outro lado da rua
    A Pizzeria Ritardato fechou pouco tempo depois
  • Na Pizzeria Veloce, haviam três garçons e dez mesas
  • Sempre que havia um pedido em uma mesa, o garçom o anotava, ia até a cozinha, entregava ao pizzaiolo e voltava imediatamente ao balcão para esperar novos pedidos
  • Quando uma pizza estava pronta, o pizzaiolo tocava uma campainha
    Um garçom que não estivesse ocupado ia até a cozinha, pegava a pizza e entregava na mesa que fez o pedido
    Não era necessariamente o mesmo garçom que anotava e entregava o pedido em uma mesa
  • Com o fechamento da Pizzeria Ritardato, o movimento de cliente da Pizzeria Veloce aumentou
    O gerente observou que da forma que trabalhavam, três garçons eram mais que o suficiente para atender as dez mesas
    Por isso, resolveu ampliar o espaço para quinze mesas, mantendo o mesmo número de garçons
  • Desta forma, os lucros aumentaram, com o crescimento das vendas sem o aumento do custo com funcionários
    Assim, a Pizzeria Veloce aumentou sua eficiência, utilizando o mesmo número de recursos para vender mais
  • Por que a Pizzeria Veloce conseguiu ser mais eficiente que a Pizzeria Ritardato?
  • Porque funcionava de forma assíncrona!
  • A principal ideia por traz da programação assíncrona é utilizar de forma eficiente os recursos computacionais
  • Consiste basicamente em não bloquear uma thread enquanto esta espera por um resultado de um processamento lento (I/O)
  • Uma thread bloqueada gasta tempo do processador
    Tempo do processador = energia elétrica = dinheiro
  • Energia elétrica = natureza
    A cada Thread.Sleep, duas espécies entram em extinção na Amazônia
  • Ao fazer uma requisição externa, a thread volta para o pool para atender outras requisições
    Quando o processamento externo termina, uma notificação é gerada para que uma thread (pode ser outra) receba o resultado e continue o processamento
  • A Pizzeria Ritardato usava mais threads que a Veloce e exatamente por isso era menos eficiente
    O objetivo é otimizar a programação paralela.
  • Na analogia com as pizzarias.
    Memória total do servidor: Orçamento da Pizzaria
  • Serviços externos: Banco de dados, API HTTP, disco, etc.
  • Existem três padrões possíveis para programação assíncrona no .NET:
    Asynchronous Programming Model (APM) – Utiliza a interface IAsyncResult, com métodos com prefixo Begin e End para a requisição e resultado. Obsoleto.
  • Event-based Asynchronous Pattern (EAP) – Requer um método para a requisição e um evento para o resultado. Obsoleto.
  • Task-based Asynchronous Pattern (TAP) – Utiliza o tipo Task<T> requer apenas um método. Recomendado.
  • É o padrão recomendado.
  • Baseia-se no uso do tipo Task ou Task<T>, onde T é o resultado do processamento da tarefa.
    Existe desde o C# 4.
    Uma Task também é chamada de Promisse ou Future (termo da teoria da computação) – Execução em potencial de uma rotina computacional
  • A rotina a ser executada é passada através de uma expressão lambda. Delegates Action ou Func
    A execução é feita por uma thread do threadpool do .NET
    O tamanho do threadpool é limitado pela memória, mas existe um limite por processo
  • Apesar de utilizar duas threads (uma principal e uma para a Task), o exemplo anterior funciona de forma síncrona, pois o processamento da primeira thread é bloqueado na chamada task.Result
  • Interface não é responsiva.
    Seria melhor se conseguíssemos ser notificados da conclusão da tarefa e só depois disso processar o resultado.
  • A classe Task permite o encadeamento de tarefas através da definição da continuação, pelo método ContinueWith.
    Desta forma, é possível evitar o bloqueio da thread principal enquanto se espera o resultado.
  • Se for uma aplicação gráfica, a thread da interface não fica bloqueada pela ação definida na tarefa.
  • Mas se o método GetName estiver realizando alguma operação de I/O, ainda existe o bloqueio da thread que está executando a tarefa (do threadpool), o que torna a aplicação ineficiente.
  • Um outro problema é que o encadeamento de continuações tende a deixar o código confuso.
  • E por último, é necessário sincronizar o contexto manualmente em aplicações gráficas, já que o ContinueWith é executado por uma thread do pool, e por isso, não tem acesso aos componentes da interface.
    E se fossem para buscar vários nomes?
  • No exemplo 1 ficaria assim
  • No exemplo 2, seria necessário recursão para garantir que as tarefas acontecam em sequencia.
  • Com o objetivo de simplificar o desenvolvimento deste tipo de aplicação, o C# 5.0 incluiu duas novas palavras-chave: async e await
  • Marcador que permite o uso de outra palavra-chave no método -> AWAIT
  • É aqui onde a mágica acontece.
    Permite “esperarmos” SEM BLOQUEIO a conclusão de uma Task
    Como resolver os problemas do nosso exemplo com estas palavras-chave?
  • Uma implementação assíncrona do método GetName, que utilize uma API assíncrona do .NET (por exemplo, HttpClient) e retorne uma Task
    Alterar o event handler do botão, incluindo a palavra-chave async, que nos permite utilizar a outra palavra-chave await
  • Implementação limpa e mais simples.
  • Versão com repetição do mesmo código
  • O que a palavra-chave async faz?
    Basicamente, ela nos permite usar no corpo do método a palavra-chave await
  • O compilador busca nos métodos marcados como async pelo uso da palavra-chave await.
    Estes pontos são como “quebras de página” de um texto, onde os métodos são separados em várias rotinas, representadas dentro de uma máquina de estados.
  • Esta máquina recebe as notificações de conclusão das Tasks avança um estado sempre que isso ocorre.
    Para o desenvolvedor, isso é transparente: A impressão é que tudo acontece de forma linear, mas na prática, o código gerado pelo compilador é complexo e adiciona um certo overhead à execução.
  • Código refatorado pelo compilador.
  • Máquina de estados gerada para o método.
  • Não valeria a pena, na perspectiva do uso de recursos, utilizar programação assíncrona com C#, devido ao overhead gerado para coordenar a execução deste tipo de código, se não houvesse o ganho que as notificações que estas APIs provêm.
  • APIs verdadeiramente assíncronas utilizam Completion Ports do sistema operacional para receber as notificações do resultado do processamento de uma requisição de I/O.
    Se não houvesse este recurso, seria necessário uma outra thread para realizar o polling e monitorar o resultado, o que tornaria o processo assíncrono tão ineficiente quanto a programação síncrona
  • As classes do .NET que expõem uma API assíncrona (TcpClient, HttpClient, etc.) contam com este recurso.
    Ou implemente a sua utilizando o TaskCompletionSource
  • Por este motivo, deve-se evitar utilizar async wrappers, a não ser que o objetivo seja apenas deixar a interface gráfica (UI) mais responsiva
  • Wrapper para um método síncrono não traz ganhos de eficiência.
    São úteis apenas em interfaces gráficas, mas se houver uma implementação assíncrona, deve ser preferida.
  • Implementação assíncrona que usa os métodos GetAsync e ReadAsStringAsync do .NET
    Métodos assíncronos sempre retornam Task e tem um sufixo Async
  • Não adianta transformar apenas parte do seu código em assíncrono. Se houver qualquer bloqueio, o desempenho pode ser pior do que a versão síncrona, devido ao overhead.
  • Toda as APIs que fazem IO e bloqueiam envolvidas na chamada devem ser assíncronas.
    Todo o callstack da chamada deve ser transformado.
  • Metodo síncrono do Web API
  • Versão assíncrono do Web API
    Basta retornar um Task<T>
    Post-fixo Async no nome do método
  • Retornar sempre Task ou Task<T>. Não usar async void em métodos.
    ConfigureAwait(false) evita a captura do contexto da requisição para a continuação, para evitar problemas com deadlocks.
  • Como as chamadas não ocorrem de forma linear, não existe um callstack disponível semelhante a do código síncrono, o que dificulta o debug do código legado.
    O ideal é logar todas as chamadas.
  • Programação assíncrona com c sharp

    1. 1. Programação assíncrona com C# 5 André Bires @ Take.net Janeiro de 2014
    2. 2. Il Pizzeria Ritardato
    3. 3. Pizzeria Ritardato 5 garçons 10 mesas
    4. 4. Pizzeria Ritardato Garçom espera na cozinha o pedido
    5. 5. Pizzeria Ritardato + de 5 pedidos = Clientes esperando
    6. 6. Pizzeria Ritardato Contratação de + 5 garçons Total = 10
    7. 7. Pizzeria Ritardato Custo > Receita = Prejuízos
    8. 8. Il Pizzeria Veloce
    9. 9. Pizzeria Veloce 3 garçons 10 mesas
    10. 10. Pizzeria Veloce Garçom leva pedido na cozinha e volta para o balcão
    11. 11. Pizzeria Veloce Campainha Garçom pedido != garçom entrega
    12. 12. Pizzeria Veloce Aumento da freguesia 3 garçons + que suficientes Ampliação para 15 mesas
    13. 13. Pizzeria Veloce Mesmo custo com Maior receita = Maior eficiência
    14. 14. ?
    15. 15. !
    16. 16. Programação assíncrona Uso eficiente dos recursos computacionais
    17. 17. NUNCA bloquear uma thread
    18. 18. Thread bloqueada = Dinheiro jogado fora
    19. 19. Thread bloqueada = Destruição da natureza
    20. 20. Programação assíncrona Retorno ao threadpool Notificação de conclusão
    21. 21. Não é o mesmo que programação paralela!
    22. 22. Programação assíncrona Pizzaria: Servidor Mesa/cliente: Aplicação Garçom: Thread Salário do garçom: Memória da thread Balcão: Thread pool
    23. 23. Programação assíncrona Pedido: Requisição externa Pizzaiolo: Serviço externo Pizza: Resultado da requisição Campainha: Notificação
    24. 24. Programação assíncrona com C# 5
    25. 25. Programação assíncrona com C# Asynchronous Programming Model (APM)
    26. 26. Programação assíncrona com C# Asynchronous Programming Model (APM) Event-based Asynchronous Pattern (EAP)
    27. 27. Programação assíncrona com C# Asynchronous Programming Model (APM) Event-based Asynchronous Pattern (EAP) Task-based Asynchronous Pattern (TAP)
    28. 28. Programação assíncrona com C# Asynchronous Programming Model (APM) Event-based Asynchronous Pattern (EAP) Task-based Asynchronous Pattern (TAP)
    29. 29. Task-based Asynchronous Pattern Task ou Task<T> C# 4 Task = Promisse ou Future
    30. 30. Task-based Asynchronous Pattern () => { } Threadpool
    31. 31. Task-based Asynchronous Pattern private void Example1Button_Click(object sender, EventArgs e)  {      toolStripStatusLabel.Text = "Calling the API...";      Task<string> task = Task.Run(() => GetName(1));      nameTextBox.Text = task.Result;      toolStripStatusLabel.Text = "Done";  }
    32. 32. Task-based Asynchronous Pattern private void Example1Button_Click(object sender, EventArgs e)  {      toolStripStatusLabel.Text = "Calling the API...";      Task<string> task = Task.Run(() => GetName(1));      nameTextBox.Text = task.Result;      toolStripStatusLabel.Text = "Done";  }
    33. 33. Task-based Asynchronous Pattern ContinueWith
    34. 34. Task-based Asynchronous Pattern private void Example2Button_Click(object sender, EventArgs e)  {      toolStripStatusLabel.Text = "Calling the API...";      Task.Run(() => GetName(1))      .ContinueWith(t =>          this.Invoke(new Action(() =>              {                  nameTextBox.Text = t.Result;                  toolStripStatusLabel.Text = "Done";              }))      ); }
    35. 35. Task-based Asynchronous Pattern private void Example2Button_Click(object sender, EventArgs e)  {      toolStripStatusLabel.Text = "Calling the API...";      Task.Run(() => GetName(1))      .ContinueWith(t =>          this.Invoke(new Action(() =>              {                  nameTextBox.Text = t.Result;                  toolStripStatusLabel.Text = "Done";              }))      ); }
    36. 36. Task-based Asynchronous Pattern private void Example2Button_Click(object sender, EventArgs e)  {      toolStripStatusLabel.Text = "Calling the API...";      Task.Run(() => GetName(1))      .ContinueWith(t =>          this.Invoke(new Action(() =>              {                  nameTextBox.Text = t.Result;                  toolStripStatusLabel.Text = "Done";              }))      ); }
    37. 37. Task-based Asynchronous Pattern private void Example2Button_Click(object sender, EventArgs e)  {      toolStripStatusLabel.Text = "Calling the API...";      Task.Run(() => GetName(1))      .ContinueWith(t =>          this.Invoke(new Action(() =>              {                  nameTextBox.Text = t.Result;                  toolStripStatusLabel.Text = "Done";              }))      ); }
    38. 38. Task-based Asynchronous Pattern private void Example1Button_Click(object sender, EventArgs e)  {      toolStripStatusLabel.Text = "Calling the API...";      for (int i = 0; i < repeatNumericUpDown.Value; i++)      {          Task<string> task = Task.Run(() => GetName(1));          nameTextBox.AppendText(task.Result);          nameTextBox.AppendText(Environment.NewLine);      }      toolStripStatusLabel.Text = "Done";  }
    39. 39. private void Example2Button_Click(object sender, EventArgs e)  {      toolStripStatusLabel.Text = "Calling the API...";      GetNames(Convert.ToInt32(repeatNumericUpDown.Value)); }  private void GetNames(int remaining)  {      Task.Run(() => GetName(1))          .ContinueWith(t =>              this.Invoke(new Action(() =>                  {                      nameTextBox.AppendText(t.Result);                      nameTextBox.AppendText(Environment.NewLine);                      remaining--;                      if (remaining > 0)                      {                          GetNames(remaining);                      }                      else                      {                          toolStripStatusLabel.Text = "Done";                      }                   })              )          );  }
    40. 40. Task-based Asynchronous Pattern C# 5.0 (.NET 4.5)
    41. 41. async
    42. 42. await
    43. 43. Task-based Asynchronous Pattern Método I/O que retorne uma Task Event handler assíncrono
    44. 44. Task-based Asynchronous Pattern private async void Example3Button_Click(object sender, EventArgs e)  {      toolStripStatusLabel.Text = "Calling the API...";      nameTextBox.Text = await GetNameAsync(1);      toolStripStatusLabel.Text = "Done";  }
    45. 45. Task-based Asynchronous Pattern private async void Example3Button_Click(object sender, EventArgs e)  {      toolStripStatusLabel.Text = "Calling the API...";      for (int i = 0; i < repeatNumericUpDown.Value; i++)      {          nameTextBox.AppendText(await GetNameAsync(1));          nameTextBox.AppendText(Environment.NewLine);      }      toolStripStatusLabel.Text = "Done";  }
    46. 46. Task-based Asynchronous Pattern private async void Example3Button_Click(object sender, EventArgs e)  {      toolStripStatusLabel.Text = "Calling the API...";      nameTextBox.Text = await GetNameAsync(1);      toolStripStatusLabel.Text = "Done";  }
    47. 47. Task-based Asynchronous Pattern private async void Example3Button_Click(object sender, EventArgs e)  {      toolStripStatusLabel.Text = "Calling the API...";      nameTextBox.Text = await GetNameAsync(1);      toolStripStatusLabel.Text = "Done";  }
    48. 48. Task-based Asynchronous Pattern private async void Example3Button_Click(object sender, EventArgs e)  {      toolStripStatusLabel.Text = "Calling the API...";      nameTextBox.Text = await GetNameAsync(1);      toolStripStatusLabel.Text = "Done";  } Primeiro estado Segundo estado
    49. 49. Task-based Asynchronous Pattern
    50. 50. Overhead !
    51. 51. Task-based Asynchronous Pattern API verdadeiramente assíncronas = Completion port (Windows)
    52. 52. Task-based Asynchronous Pattern TcpClient HttpClient Socket Etc.
    53. 53. Task-based Asynchronous Pattern Async wrappers = EVIL
    54. 54. Task-based Asynchronous Pattern private Task<string> GetNameAsyncWrapper(int id) {      return Task.Run(() =>      {          return GetName(id);      }); }  private string GetName(int id)  {      // Synchronous implemementation  }
    55. 55. Task-based Asynchronous Pattern private Task<string> GetNameAsyncWrapper(int id) {      return Task.Run(() =>      {          return GetName(id);      }); }  private string GetName(int id)  {      // Synchronous implemementation  }
    56. 56. Task-based Asynchronous Pattern private async Task<string> GetNameAsync(int id) {     string url = string.Format("http://localhost:3803/names/{0}", id);       using (var client = new HttpClient())     {         var httpResponseMessage = await client.GetAsync(url);           if (httpResponseMessage.IsSuccessStatusCode)         {             return await httpResponseMessage.Content                 .ReadAsStringAsync();         }         else         {             return httpResponseMessage.ReasonPhrase;         }     } }
    57. 57. Task-based Asynchronous Pattern Código legado requer muitas alterações
    58. 58. async = infecçã o
    59. 59. Task-based Asynchronous Pattern public string Get(int id, bool delay = false) {      if (delay)      {          // Simula uma chamada externa, como banco de dados          Thread.Sleep(3000);      }      return string.Format(          "{0} {1}",          _firstNames[_random.Next(_firstNames.Length - 1)],          _lastNames[_random.Next(_lastNames.Length - 1)]);  }
    60. 60. Task-based Asynchronous Pattern public async Task<string> GetAsync(int id, bool delay = false)  {      if (delay)      {          // Simula uma chamada externa, como banco de dados          await Task.Delay(3000);      }      return string.Format(          "{0} {1}",          _firstNames[_random.Next(_firstNames.Length - 1)],          _lastNames[_random.Next(_lastNames.Length - 1)]);  }
    61. 61. Task-based Asynchronous Pattern Boas práticas: async void apenas em event handlers ConfigureAwait(false) em bibliotecas
    62. 62. Task-based Asynchronous Pattern Callstack é diferente do código síncrono
    63. 63. ?

    ×