Eu vim aqui bastante confortável com os dois conceitos, mas com algo não claro para mim sobre eles.
Depois de ler algumas das respostas, acho que tenho uma metáfora correta e útil para descrever a diferença.
Se você pensa em suas linhas de código individuais como cartas de baralho separadas mas ordenadas (pare-me se eu estiver explicando como os cartões perfurados da velha escola funcionam), então, para cada procedimento separado escrito, você terá uma pilha única de cartas (não copiar e colar!) ea diferença entre o que normalmente acontece quando o código é executado normalmente e de forma assíncrona depende se você se importa ou não.
Ao executar o código, você entrega ao sistema operacional um conjunto de operações únicas (nas quais seu compilador ou intérprete quebrou o código de nível "superior") a ser passado ao processador. Com um processador, apenas uma linha de código pode ser executada ao mesmo tempo. Portanto, para realizar a ilusão de executar vários processos ao mesmo tempo, o sistema operacional usa uma técnica na qual envia ao processador apenas algumas linhas de um determinado processo por vez, alternando entre todos os processos de acordo com a forma como vê em forma. O resultado são vários processos que mostram o progresso para o usuário final no que parece ser ao mesmo tempo.
Para nossa metáfora, o relacionamento é que o sistema operacional sempre embaralha as placas antes de enviá-las ao processador. Se sua pilha de cartas não depender de outra pilha, você não notará que sua pilha parou de ser selecionada enquanto outra pilha se tornou ativa. Então, se você não se importa, não importa.
No entanto, se você se importa (por exemplo, existem vários processos - ou pilhas de cartões - que dependem um do outro), o embaralhamento do sistema operacional estraga seus resultados.
A escrita de código assíncrono exige o tratamento das dependências entre a ordem de execução, independentemente do que essa ordem acaba sendo. É por isso que construções como "retornos de chamada" são usadas. Eles dizem ao processador: "a próxima coisa a fazer é contar à outra pilha o que fizemos". Ao usar essas ferramentas, você pode ter certeza de que a outra pilha é notificada antes de permitir que o sistema operacional execute mais instruções. ("Se chamado_back == false: send (no_operation)" - não tenho certeza se é assim que realmente é implementado, mas logicamente, acho que é consistente.)
Para processos paralelos, a diferença é que você tem duas pilhas que não se importam uma com a outra e dois trabalhadores para processá-las. No final do dia, pode ser necessário combinar os resultados das duas pilhas, o que seria uma questão de sincronicidade, mas, para execução, você não se importa mais.
Não tenho certeza se isso ajuda, mas sempre achei várias explicações úteis. Além disso, observe que a execução assíncrona não é restrita a um computador individual e seus processadores. De um modo geral, lida com o tempo ou (ainda mais de maneira geral) uma ordem de eventos. Portanto, se você enviar a pilha dependente A para o nó de rede X e sua pilha acoplada B para Y, o código assíncrono correto deverá ser capaz de dar conta da situação como se estivesse em execução localmente no seu laptop.