Przyjechałem tutaj dość dobrze z tymi dwoma koncepcjami, ale z czymś nie do końca jasnym na ich temat.
Po przeczytaniu niektórych odpowiedzi myślę, że mam poprawną i pomocną metaforę do opisania różnicy.
Jeśli myślisz o swoich indywidualnych liniach kodu jako o oddzielnych, ale uporządkowanych kartach do gry (zatrzymaj mnie, jeśli wyjaśnię, jak działają oldschoolowe karty ponczowe), to dla każdej oddzielnej procedury będziesz mieć unikalny stos kart (nie kopiuj i wklej!), a różnica między tym, co normalnie dzieje się, gdy kod jest uruchamiany normalnie i asynchronicznie, zależy od tego, czy Ci zależy, czy nie.
Kiedy uruchamiasz kod, przekazujesz systemowi operacyjnemu zestaw pojedynczych operacji (na które Twój kompilator lub interpreter złamał twój kod „wyższego” poziomu), które mają być przekazane do procesora. W przypadku jednego procesora w danym momencie można wykonać tylko jedną linię kodu. Tak więc, aby uzyskać iluzję uruchamiania wielu procesów w tym samym czasie, system operacyjny wykorzystuje technikę, w której wysyła procesorowi tylko kilka wierszy z danego procesu na raz, przełączając się między wszystkimi procesami zgodnie z tym, jak widzi dopasowanie. Rezultatem jest wiele procesów pokazujących postęp użytkownikowi końcowemu w tym samym czasie.
Według naszej metafory związek jest taki, że system operacyjny zawsze tasuje karty przed wysłaniem ich do procesora. Jeśli twój stos kart nie zależy od innego stosu, nie zauważysz, że twój stos przestał być wybierany, gdy inny stos stał się aktywny. Więc jeśli cię to nie obchodzi, to nie ma znaczenia.
Jeśli jednak Cię to obchodzi (np. Istnieje wiele procesów - lub stosy kart - które są od siebie zależne), tasowanie systemu operacyjnego zepsuje wyniki.
Pisanie kodu asynchronicznego wymaga obsługi zależności między kolejnością wykonywania, niezależnie od tego, czym skończy się ta kolejność. Właśnie dlatego używane są konstrukcje takie jak „call-backs”. Mówią do procesora: „Następną rzeczą do zrobienia jest poinformowanie drugiego stosu, co zrobiliśmy”. Korzystając z takich narzędzi, można mieć pewność, że drugi stos zostanie powiadomiony, zanim zezwoli systemowi operacyjnemu na wykonanie dalszych instrukcji. („If named_back == false: send (no_operation)” - nie jestem pewien, czy tak jest faktycznie zaimplementowane, ale logicznie myślę, że jest to spójne.)
W przypadku procesów równoległych różnica polega na tym, że masz dwa stosy, które nie dbają o siebie nawzajem, oraz dwóch pracowników przetwarzających je. Pod koniec dnia może być konieczne połączenie wyników z dwóch stosów, co byłoby wówczas kwestią synchronizacji, ale w przypadku wykonania znowu nie obchodzi cię to.
Nie jestem pewien, czy to pomaga, ale zawsze uważam, że wiele wyjaśnień jest pomocnych. Należy również zauważyć, że wykonywanie asynchroniczne nie jest ograniczone do pojedynczego komputera i jego procesorów. Ogólnie rzecz biorąc, dotyczy czasu lub (jeszcze bardziej ogólnie) kolejności wydarzeń. Jeśli więc wyślesz zależny stos A do węzła sieciowego X i jego sprzężony stos B do Y, prawidłowy kod asynchroniczny powinien być w stanie uwzględnić sytuację, tak jakby działał lokalnie na twoim laptopie.