Pergunta

Atualmente, tenho um grande número de cálculos C# (chamadas de método) residentes em uma fila que será executada sequencialmente.Cada computação usará algum serviço de alta latência (rede, disco...).

Eu usaria corrotinas Mono para permitir que o próximo cálculo na fila de computação continuasse enquanto um cálculo anterior aguardava o retorno do serviço de alta latência.No entanto, prefiro não depender de corrotinas Mono.

Existe um padrão de design implementável em C# puro que me permitirá processar cálculos adicionais enquanto aguardo o retorno de serviços de alta latência?

Obrigado

Atualizar:

Preciso executar um grande número (> 10.000) de tarefas, e cada tarefa usará algum serviço de alta latência.No Windows, você não pode criar tantos threads.

Atualizar:

Basicamente, preciso de um padrão de design que emule as vantagens (como segue) dos tasklets no Stackless Python (http://www.stackless.com/)

  1. Grande número de tarefas
  2. Se uma tarefa bloquear a próxima tarefa na fila será executada
  3. Nenhum ciclo de CPU desperdiçado
  4. Sobrecarga mínima na alternância entre tarefas
Foi útil?

Solução

Você pode simular a microthreading cooperativa usando o IENUMERABER. Infelizmente, isso não funcionará com APIs de bloqueio, então você precisa encontrar APIs que pode pesquisar ou que possuem retornos de chamada que você pode usar para sinalizar.

Considere um método

IEnumerable Thread ()
{
    //do some stuff
    Foo ();

    //co-operatively yield
    yield null;

    //do some more stuff
    Bar ();

    //sleep 2 seconds
    yield new TimeSpan (2000);
}

O compilador C# desembrulhará isso em uma máquina de estado - mas a aparência é a de um microthread cooperativo.

O padrão é bastante direto. Você implementa um "agendador" que mantém uma lista de todos os ienumeradores ativos. À medida que percorre a lista, ele "executa" cada um usando o MoveNeNext (). Se o valor do Movenext for falso, o thread terminou e o agendador o remover da lista. Se for verdade, o agendador acessa a propriedade atual para determinar o estado atual do thread. Se for um timepan, o fio deseja dormir, e o agendador o moveu para uma fila que pode ser descartada de volta para a lista principal quando os horários do sono terminarem.

Você pode usar outros objetos de retorno para implementar outros mecanismos de sinalização. Por exemplo, defina algum tipo de WaitHandle. Se a rosca produzir uma delas, poderá ser movida para uma fila de espera até que a alça seja sinalizada. Ou você pode apoiar o WaitAll, produzindo uma variedade de alças de espera. Você pode até implementar prioridades.

Fiz uma implementação simples deste agendador em cerca de 150LOC, mas ainda não tenho baldes para blogar o código. Foi para o nosso invólucro phyresharp phyreengine (que não será público), onde parece funcionar muito bem para controlar algumas centenas de personagens em uma de nossas demos. Emprestamos o conceito do mecanismo Unity3D - eles têm alguns documentos on -line que o explicam do ponto de vista do usuário.

Outras dicas

Eu recomendo usar o Grupo de discussão para executar várias tarefas da sua fila de uma só vez em lotes gerenciáveis ​​usando uma lista de tarefas ativas que se alimenta da fila de tarefas.

Neste cenário, seu thread de trabalho principal inicialmente colocaria N tarefas da fila na lista de tarefas ativas para serem despachadas para o pool de threads (provavelmente usando QueueUserWorkItem), onde N representa uma quantidade gerenciável que não sobrecarregará o pool de threads, sobrecarregará seu aplicativo com agendamento de threads e custos de sincronização ou consumirá memória disponível devido à sobrecarga de memória de E/S combinada de cada tarefa.

Sempre que uma tarefa sinaliza a conclusão para o thread de trabalho, você pode removê-la da lista de tarefas ativas e adicionar a próxima da fila de tarefas a ser executada.

Isso permitirá que você tenha um conjunto contínuo de N tarefas da sua fila.Você pode manipular N para afetar as características de desempenho e descobrir o que é melhor em suas circunstâncias específicas.

Como você fica com gargalos nas operações de hardware (E/S de disco e E/S de rede, CPU), imagino que quanto menor, melhor.Duas tarefas de pool de threads trabalhando em E/S de disco provavelmente não serão executadas mais rápido que uma.

Você também pode implementar flexibilidade no tamanho e no conteúdo da lista de tarefas ativas, restringindo-a a um determinado número de tipos específicos de tarefas.Por exemplo, se você estiver executando em uma máquina com 4 núcleos, poderá descobrir que a configuração de maior desempenho é de quatro tarefas vinculadas à CPU sendo executadas simultaneamente, juntamente com uma tarefa vinculada ao disco e uma tarefa de rede.

Se você já tiver uma tarefa classificada como tarefa de E/S de disco, poderá optar por aguardar até que ela seja concluída antes de adicionar outra tarefa de E/S de disco e, enquanto isso, poderá optar por agendar uma tarefa vinculada à CPU ou à rede.

Espero que isso faça sentido!

PS:Você tem alguma dependência na ordem das tarefas?

Você definitivamente deve conferir o Tempo de execução de simultaneidade e coordenação. Uma de suas amostras descreve exatamente o que você está falando: você chama os serviços de longa latência e o CCR permite que alguma outra tarefa seja executada enquanto espera. Ele pode lidar com um grande número de tarefas, pois não precisa gerar um thread para cada um, embora ele use todos os seus núcleos se você pedir.

Não é um uso convencional de processamento multithread?

Dê uma olhada em padrões como o reator aqui

Escrevendo para usar Assíncrono io pode ser suficiente.

Isso pode levar a Nasy, difícil depurar código sem estrutura forte no design.

Você deve dar uma olhada nisso:

http://www.replicator.org/node/80

Isso deve fazer exatamente o que você deseja. É um hack, no entanto.

Algumas informações sobre o padrão "reativo" (como mencionado por outro pôster) em relação a uma implementação no .NET; aka "Linq to Events"

http://themechanicalbride.blogspot.com/2009/07/introducing-rx-linq-to-events.html

-Oisin

De fato, se você usar um tópico para uma tarefa, perderá o jogo. Pense no motivo pelo qual o Node.js pode suportar um grande número de conexões. Usando algum número de threads com io assíncrono !!! As funções assíncronas e aguardam podem ajudar nisso.

foreach (var task in tasks)
{
    await SendAsync(task.value);
    ReadAsync(); 
}

SendAsync () e readaSync () são funções falsificadas para a chamada assíncrona.

Paralelismo da tarefa também é uma boa escolha. Mas não tenho certeza de qual é mais rápido. Você pode testar os dois no seu caso.

Sim, é claro que você pode. Você só precisa criar um mecanismo de despachante que retorne a um lambda que você fornece e entra em uma fila. Todo o código que escrevo no Unity usa essa abordagem e nunca uso coroutinas. Eu envolvo métodos que usam coroutinas, como o WWW Stuff, para se livrar dele. Em teoria, as coroutinas podem ser mais rápidas porque há menos sobrecarga. Praticamente eles introduzem uma nova sintaxe a um idioma para realizar uma tarefa bastante trivial e, além disso, você não pode seguir o rastreamento da pilha corretamente em um erro em uma co -rotina, porque tudo o que você verá é -> a seguir. Você terá que implementar a capacidade de executar as tarefas na fila em outro thread. No entanto, existem funções paralelas no .NET mais recente e você estaria essencialmente escrevendo funcionalidade semelhante. Na verdade, não seriam muitas linhas de código.

Se alguém estiver interessado, enviaria o código, não o tenha.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top