Parallel.ForEach vs Async Forloop em operações de E / S pesadas

Eu quero comparar dois cenários teóricos. Simplifiquei os casos para fins da pergunta. Mas basicamente é o seu cenário típico de consumidor produtor. (Estou focando no consumidor).

Eu tenho um grandeQueue<string> dataQueue que eu tenho que transmitir para vários clientes.

Então, vamos começar com o caso mais simples:

 class SequentialBlockingCase
 {
    public static Queue<string> DataQueue = new Queue<string>();
    private static List<string> _destinations = new List<string>();

    /// <summary>
    /// Is the main function that is run in its own thread
    /// </summary>
    private static void Run()
    {
        while (true)
        {
            if (DataQueue.Count > 0)
            {
                string data = DataQueue.Dequeue();
                foreach (var destination in _destinations)
                {
                    SendDataToDestination(destination, data);
                }
            }
            else
            {
                Thread.Sleep(1);
            }
        }
    }

    private static void SendDataToDestination(string destination, string data)
    {
        //TODO: Send data using http post, instead simulate the send
        Thread.Sleep(200);
    }
}
}

Agora essa configuração funciona muito bem. Ele fica lá e pesquisa oQueue e quando houver dados para enviá-lo, ele será enviado para todos os destinos.

Problemas:

Se um dos destinos estiver indisponível ou lento, afetará todos os outros destinos.Não utiliza multiencadeamento no caso de execução paralela.Blocos para cada transmissão para cada destino.

Então, aqui está minha segunda tentativa:

 class ParalleBlockingCase
{
    public static Queue<string> DataQueue = new Queue<string>();
    private static List<string> _destinations = new List<string>();

    /// <summary>
    /// Is the main function that is run in its own thread
    /// </summary>
    private static void Run()
    {
        while (true)
        {
            if (DataQueue.Count > 0)
            {
                string data = DataQueue.Dequeue();
                Parallel.ForEach(_destinations, destination =>
                {
                    SendDataToDestination(destination, data);
                });
            }
            else
            {
                Thread.Sleep(1);
            }
        }
    }

    private static void SendDataToDestination(string destination, string data)
    {
        //TODO: Send data using http post
        Thread.Sleep(200);
    }
}

Esta revisão pelo menos não afeta os outros destinos se 1 destino estiver lento ou indisponível.

No entanto, esse método ainda está bloqueando e não tenho certeza seParallel.ForEach faz uso do pool de threads. Meu entendimento é que ele criará um número X de threads / tarefas e executará 4 (4 núcleos da CPU) por vez. Mas ele precisa executar a tarefa 1 completamente finlandesa antes que a tarefa 5 possa ser iniciada.

Daí a minha terceira opção:

class ParalleAsyncCase
{
    public static Queue<string> DataQueue = new Queue<string>();
    private static List<string> _destinations = new List<string> { };

    /// <summary>
    /// Is the main function that is run in its own thread
    /// </summary>
    private static void Run()
    {
        while (true)
        {
            if (DataQueue.Count > 0)
            {
                string data = DataQueue.Dequeue();
                List<Task> tasks = new List<Task>();
                foreach (var destination in _destinations)
                {
                    var task = SendDataToDestination(destination, data);
                    task.Start();
                    tasks.Add(task);
                }

                //Wait for all tasks to complete
                Task.WaitAll(tasks.ToArray());
            }
            else
            {
                Thread.Sleep(1);
            }
        }
    }

    private static async Task SendDataToDestination(string destination, string data)
    {
        //TODO: Send data using http post
        await Task.Delay(200);
    }
}

Agora, pelo que entendi, essa opção ainda bloqueará o thread principal emTask.WaitAll(tasks.ToArray()); o que é bom, porque não quero que ele crie tarefas mais rapidamente do que elas podem ser executadas.

Mas as tarefas que serão executadas em paralelo devem fazer uso doThreadPool, e todo o número X de tarefas deve começar a executar ao mesmo tempo, sem bloquear ou em ordem seqüencial. (o pool de threads será trocado entre eles à medida que se tornarem ativos ou foremawaiting)

Agora minha pergunta.

A opção 3 tem algum benefício de desempenho sobre a opção 2.

Especificamente em um cenário do servidor com desempenho mais alto. No software específico em que estou trabalhando agora. Haveria várias instâncias do meu caso de uso simples acima. Ou seja, vários consumidores.

Estou interessado nas diferenças teóricas e prós e contras das duas soluções, e talvez até uma quarta opção melhor, se houver uma.

questionAnswers(2)

yourAnswerToTheQuestion