> For the complete documentation index, see [llms.txt](https://docs.skail.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.skail.dev/construa/como-fazer/rodar-em-paralelo-e-juntar.md).

# Rodar em paralelo e juntar

Como disparar vários commands ao mesmo tempo dentro de uma function, esperar todos ou o primeiro que terminar, e ler os resultados sem depender da ordem de término.

## Fan-out e fan-in: esperar todos

Consultar três fornecedores e escolher o menor preço:

{% tabs %}
{% tab title="C#" %}

```csharp
public record Cotacao(string Fornecedor, decimal Preco);

[SkailFunction]
public async SkailTask<Cotacao> MelhorCotacao(Guid produtoId)
{
    var a = CotarFornecedorA(produtoId);          // sem await: as três começam
    var b = CotarFornecedorB(produtoId);
    var c = CotarFornecedorC(produtoId);

    await SkailTask.WhenAll(a, b, c);             // espera as três

    var cotacoes = new[] { await a, await b, await c };   // resultado pela posição
    return cotacoes.OrderBy(x => x.Preco).First();
}

[SkailCommand(retryCount: 8)]
public async SkailTask<Cotacao> CotarFornecedorA(Guid produtoId)
    => new Cotacao("A", await _fornecedorA.CotarAsync(produtoId));

[SkailCommand(retryCount: 8)]
public async SkailTask<Cotacao> CotarFornecedorB(Guid produtoId)
    => new Cotacao("B", await _fornecedorB.CotarAsync(produtoId));

[SkailCommand(retryCount: 8)]
public async SkailTask<Cotacao> CotarFornecedorC(Guid produtoId)
    => new Cotacao("C", await _fornecedorC.CotarAsync(produtoId));
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { Skail, SkailCommand, SkailFunction } from "@skail/runtime";

export interface Cotacao {
  fornecedor: string;
  preco: number;
}

interface Fornecedor {
  cotar(produtoId: string): Promise<number>;
}

class FornecedorHttp implements Fornecedor {
  constructor(private readonly baseUrl: string) {}

  async cotar(produtoId: string): Promise<number> {
    const resposta = await fetch(`${this.baseUrl}/cotacoes/${produtoId}`);
    if (!resposta.ok) throw new Error(`cotação falhou: ${resposta.status}`);
    return ((await resposta.json()) as { preco: number }).preco;
  }
}

export class CotacaoService {
  private readonly fornecedorA: Fornecedor = new FornecedorHttp("https://a.exemplo.com");
  private readonly fornecedorB: Fornecedor = new FornecedorHttp("https://b.exemplo.com");
  private readonly fornecedorC: Fornecedor = new FornecedorHttp("https://c.exemplo.com");

  @SkailFunction()
  async MelhorCotacao(produtoId: string): Promise<Cotacao> {
    const a = this.CotarFornecedorA(produtoId);          // sem await: as três começam
    const b = this.CotarFornecedorB(produtoId);
    const c = this.CotarFornecedorC(produtoId);

    // espera as três; resultado pela posição
    const cotacoes = await Skail.whenAll(a, b, c);
    return [...cotacoes].sort((x, y) => x.preco - y.preco)[0]!;
  }

  @SkailCommand({ retryCount: 8 })
  async CotarFornecedorA(produtoId: string): Promise<Cotacao> {
    return { fornecedor: "A", preco: await this.fornecedorA.cotar(produtoId) };
  }

  @SkailCommand({ retryCount: 8 })
  async CotarFornecedorB(produtoId: string): Promise<Cotacao> {
    return { fornecedor: "B", preco: await this.fornecedorB.cotar(produtoId) };
  }

  @SkailCommand({ retryCount: 8 })
  async CotarFornecedorC(produtoId: string): Promise<Cotacao> {
    return { fornecedor: "C", preco: await this.fornecedorC.cotar(produtoId) };
  }
}
```

{% endtab %}
{% endtabs %}

Os commands são criados sem `await`, para que os três iniciem, e a primitiva que espera todos aguarda os três. Os resultados saem sem esperar de novo: em C#, `await a` devolve o resultado gravado de `a`; em TypeScript, a espera devolve os resultados na ordem dos argumentos. Ler pela posição (`a`, `b`, `c`) é o que garante determinismo: a ordem em que terminaram não importa e não é reproduzível.

Cada ramificação grava o próprio passo. Se `b` falhar e esgotar as tentativas, a exceção chega à function no ponto em que ela espera todos; `a` e `c`, se concluíram, ficam gravados e não repetem na retomada.

## Quantidade variável

Uma lista de ids:

{% tabs %}
{% tab title="C#" %}

```csharp
[SkailFunction]
public async SkailTask EmitirLote(Guid[] pedidoIds)
{
    var emissoes = pedidoIds.Select(EmitirNota).ToArray();     // SkailTask[] na ordem dos ids
    await SkailTask.WhenAll(emissoes);

    for (var i = 0; i < pedidoIds.Length; i++)
    {
        var chave = await emissoes[i];
        await RegistrarChave(pedidoIds[i], chave);
    }
}

[SkailCommand]
public async SkailTask<string> EmitirNota(Guid pedidoId) { /* ... */ }
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { Skail, SkailCommand, SkailFunction } from "@skail/runtime";

export class LoteService {
  @SkailFunction()
  async EmitirLote(pedidoIds: string[]): Promise<void> {
    const emissoes = pedidoIds.map((id) => this.EmitirNota(id));   // Promise<string>[] na ordem dos ids
    const chaves = await Skail.whenAll(emissoes);

    for (let i = 0; i < pedidoIds.length; i++) {
      await this.RegistrarChave(pedidoIds[i]!, chaves[i]!);
    }
  }

  @SkailCommand()
  async EmitirNota(pedidoId: string): Promise<string> { /* ... */ return `NFE-${pedidoId}`; }

  @SkailCommand()
  async RegistrarChave(pedidoId: string, chave: string): Promise<void> {}
}
```

{% endtab %}
{% endtabs %}

A ordem da lista é a ordem dos resultados. Lotes muito grandes geram muitos passos por execução; para milhares de itens, divida em sub-lotes ou faça uma function por item disparada por trigger. Ver [Limites e cotas](/referencia/limites-e-cotas.md).

## O primeiro que responder

Esperar o primeiro que terminar entrega o resultado de quem concluiu primeiro: em C#, a primitiva devolve a task vencedora, e um `await` nela lê o resultado; em TypeScript, ela devolve o próprio resultado. O uso mais comum é dar prazo a uma espera ([Como esperar com timeout](/construa/como-fazer/esperar-um-evento-com-prazo.md)); o segundo é redundância: consultar dois provedores e usar o primeiro que responder.

{% tabs %}
{% tab title="C#" %}

```csharp
var primario   = ConsultarProvedorPrimario(cpf);
var secundario = ConsultarProvedorSecundario(cpf);

var vencedor = await SkailTask.WhenAny(primario, secundario);
var resultado = vencedor == primario ? await primario : await secundario;
```

{% endtab %}

{% tab title="TypeScript" %}

```typescript
import { Skail, SkailCommand, SkailFunction } from "@skail/runtime";

export interface ResultadoProvedor {
  provedor: string;
  score: number;
}

export class ConsultaService {
  @SkailFunction()
  async ConsultarCpf(cpf: string): Promise<ResultadoProvedor> {
    const primario   = this.ConsultarProvedorPrimario(cpf);
    const secundario = this.ConsultarProvedorSecundario(cpf);

    const resultado = await Skail.whenAny(primario, secundario);
    return resultado;
  }

  @SkailCommand()
  async ConsultarProvedorPrimario(cpf: string): Promise<ResultadoProvedor> {
    return { provedor: "primario", score: 700 };
  }

  @SkailCommand()
  async ConsultarProvedorSecundario(cpf: string): Promise<ResultadoProvedor> {
    return { provedor: "secundario", score: 700 };
  }
}
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
A ramificação que não terminou primeiro continua em segundo plano até concluir ou até a function terminar. Um aviso real: com muitas execuções esperando e poucas instâncias, qual ramificação conclui primeiro depende da ordem em que o trabalho é processado, então esperar o primeiro como "o mais rápido" precisa de instâncias suficientes para ser fiel à latência real.
{% endhint %}

## Paralelismo dentro de um command

Dentro de um command, a concorrência comum da linguagem é permitida: o command não é replayado depois de gravar o resultado. Use isso quando o paralelismo é um detalhe de implementação de uma única operação externa (baixar três arquivos para montar um relatório). Use a espera de todos do SDK na function quando cada ramificação é um passo que merece o próprio checkpoint, retry e linha no Monitor. Na function, a regra se inverte: só as primitivas do SDK esperam chamadas do SDK (em C#, nada de `Task.WhenAll`; em TypeScript, nada de `Promise.all`, `Promise.race` ou `.then`).

## Erros comuns

| Sintoma                                | Causa                                                                             | Correção                                         |
| -------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------ |
| Resultados trocados entre fornecedores | Leu pela ordem de término, ou por uma lista montada em ordem diferente da criação | Leia pela posição de cada chamada que você criou |
| (C#) `SkailNonDeterministicException`  | `Task.WhenAll` na function em vez de `SkailTask.WhenAll`                          | Use as versões `SkailTask`                       |
| As ramificações rodam em sequência     | `await` em cada chamada antes de esperar todos                                    | Crie sem `await` e espere todas de uma vez       |

## Próximos passos

[Fan-out / Fan-in](/construa/como-fazer/padroes-e-antipadroes/fan-out-fan-in.md) é o padrão completo com agregação. A referência de [WhenAll e WhenAny](/referencia/sdk-csharp/whenall-e-whenany.md) no SDK C#.
