Menu

Queue em C#: Enqueue, Dequeue, Peek e TryDequeue

Queue<T> é uma coleção em que o primeiro a entrar é o primeiro a sair: os itens saem na ordem em que chegaram. Veja Enqueue, Dequeue e Peek, a exceção da fila vazia e o TryDequeue, busca em largura com uma fila e quando usar ConcurrentQueue ou PriorityQueue.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Uma Queue<T> funciona como uma fila no balcão: o primeiro item adicionado é o primeiro a sair (FIFO, first in, first out). Você adiciona no fim com Enqueue e tira da frente com Dequeue, os dois em tempo constante.

Enqueue, Dequeue e Peek

Saída:

Waiting: 3
Next up: invoice.pdf
Printing invoice.pdf
Next up: photo.png
Printing photo.png
Printing report.docx
Waiting: 0

Peek serve para decidir o que fazer com o próximo item antes de se comprometer com ele; Dequeue se compromete. O laço while (queue.Count > 0) que faz dequeue até esvaziar é a forma padrão de processar uma fila, e ele continua funcionando se o corpo do laço enfileirar mais itens, o que um foreach não permite.

A exceção da fila vazia e o TryDequeue

Dequeue e Peek em uma fila vazia lançam InvalidOperationException. Não existe o comportamento "null quando vazia", nem para tipos de referência.

Saída:

Caught InvalidOperationException
Serving ticket 41
Serving ticket 42
False

TryDequeue e TryPeek retornam true e definem a variável out quando há um item, e retornam false quando a fila está vazia. Eles chegaram no .NET Core 2.0; em código mais antigo do .NET Framework, verifique Count > 0 antes de chamar Dequeue.

Olhando dentro de uma fila

Uma fila pode ser enumerada sem remover nada. foreach, ToArray e Contains veem os itens da frente para o fim.

Saída:

Ana Ben Chloe 
True
Ana is first, 3 in line
3

Não há índice: line[1] não compila, e você não pode remover um item do meio. Se você precisa de alguma dessas coisas, os dados na verdade não são uma fila; use uma List<T> ou uma LinkedList<T>. Como em outras coleções, enfileirar ou desenfileirar dentro de um foreach sobre a mesma fila lança InvalidOperationException.

Busca em largura com uma fila

A ordem da fila é exatamente o que a busca em largura precisa: visitar tudo o que está a um passo, depois tudo o que está a dois passos, e assim por diante. Aqui ela descobre quantas conexões separam Ana de todas as outras pessoas em uma rede pequena:

Saída:

Ana: 0 step(s) from Ana
Ben: 1 step(s) from Ana
Chloe: 1 step(s) from Ana
Dev: 2 step(s) from Ana
Eli: 2 step(s) from Ana
Fay: 3 step(s) from Ana

Cada pessoa é enfileirada uma vez, na primeira vez em que é alcançada, e como a fila as devolve na ordem em que foram encontradas, essa primeira vez sempre segue um caminho mais curto. Troque a fila por uma Stack e o mesmo laço vira uma busca em profundidade, que não encontra mais os caminhos mais curtos.

A mesma forma resolve o percurso em nível de uma árvore, o flood fill em uma grade e o rastreamento de links: comece com um item na fila e, enquanto ela não estiver vazia, desenfileire um e enfileire os vizinhos ainda não visitados.

Queue vs List para trabalho FIFO

Você pode usar uma List<T> como fila com Add e RemoveAt(0), mas RemoveAt(0) desloca todos os elementos restantes uma posição para baixo. Esvaziar assim uma lista de 100.000 itens faz cerca de cinco bilhões de movimentações de elementos; uma Queue<T> faz 100.000 passos de tempo constante. Por dentro, uma fila é um buffer circular: ela acompanha um índice de início e um de fim em um array e só realoca quando enche.

OperaçãoQueue<T>List<T> usada como fila
Adicionar no fimEnqueue, O(1)Add, O(1)
Remover da frenteDequeue, O(1)RemoveAt(0), O(n)
Ver a frentePeeklist[0]
Acesso por índiceNão disponívellist[i]

ConcurrentQueue para várias threads

Queue<T> não é thread safe. Quando várias threads adicionam ou retiram itens, use ConcurrentQueue<T> de System.Collections.Concurrent. Ela tem Enqueue, TryDequeue e TryPeek, mas não Dequeue, porque com outras threads por perto, "verificar Count e depois fazer Dequeue" poderia falhar entre as duas chamadas.

Saída:

4000
4000 processed

Com uma Queue<T> comum no lugar de ConcurrentQueue<T>, a contagem sairia errada ou o programa lançaria uma exceção, dependendo do momento. Para threads produtoras e consumidoras que devem esperar por trabalho em vez de ficar girando, BlockingCollection<T> (que por padrão envolve uma ConcurrentQueue<T>) ou System.Threading.Channels adicionam bloqueio e conclusão. Veja lock para proteger uma coleção normal manualmente.

PriorityQueue

Quando os itens devem sair por prioridade em vez de pela ordem de chegada (o chamado mais urgente primeiro, o caminho mais curto até agora no algoritmo de Dijkstra), o .NET 6 em diante oferece PriorityQueue<TElement, TPriority>. O menor valor de prioridade sai primeiro:

var triage = new PriorityQueue<string, int>();
triage.Enqueue("sprained ankle", 3);
triage.Enqueue("chest pain", 1);
triage.Enqueue("headache", 5);

while (triage.TryDequeue(out string patient, out int priority))
{
    Console.WriteLine($"{priority}: {patient}");
}
// 1: chest pain
// 3: sprained ankle
// 5: headache

Itens com a mesma prioridade podem sair em qualquer ordem; adicione um número de sequência à prioridade se a ordem de chegada precisar desempatar.

Erros comuns

  • Fazer Dequeue sem verificar. Uma fila vazia lança InvalidOperationException; faça o laço com Count > 0 ou use TryDequeue.
  • Chamar Peek e esperar que o item tenha saído. Só o Dequeue remove.
  • Enfileirar dentro de um foreach sobre a fila. Lança exceção; processe com um laço while.
  • Compartilhar uma Queue<T> entre threads. Use ConcurrentQueue<T> ou um lock.
  • Usar List.RemoveAt(0) como fila em dados grandes. Cada chamada é O(n).

Perguntas frequentes

O que é uma Queue em C#?

Queue<T>, em System.Collections.Generic, é uma coleção em que o primeiro a entrar é o primeiro a sair (FIFO). Enqueue adiciona um item no fim, Dequeue remove e retorna o item da frente, e Peek retorna o item da frente sem removê-lo. Os três levam tempo constante.

O que acontece ao fazer Dequeue em uma fila vazia em C#?

Dequeue e Peek em uma fila vazia lançam InvalidOperationException. Verifique queue.Count > 0 antes, ou use TryDequeue(out var item) e TryPeek(out var item), que retornam false em vez de lançar exceção (disponíveis desde o .NET Core 2.0).

Qual a diferença entre Peek e Dequeue?

Peek retorna o item da frente e o deixa na fila, então chamá-lo duas vezes retorna o mesmo item. Dequeue retorna o item da frente e o remove, então a próxima chamada retorna o item que estava atrás dele.

Queue<T> é thread safe em C#?

Não. Duas threads chamando Enqueue ou Dequeue na mesma Queue<T> ao mesmo tempo podem corrompê-la. Use ConcurrentQueue<T> de System.Collections.Concurrent, cujos Enqueue e TryDequeue podem ser chamados com segurança de muitas threads, ou envolva todo acesso a uma fila normal em um lock.

Por que usar uma Queue em vez de uma List?

Remover o primeiro item de uma List<T> com RemoveAt(0) desloca todos os outros itens, então fica mais lento à medida que a lista cresce. Uma Queue<T> remove da frente em tempo constante, e a API dela deixa a intenção clara: os itens são processados na ordem de chegada.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR