Ho programmato per molto tempo, ma molto raramente con qualcosa di asincrono (e spesso non ha nulla a che fare con il multithreading).
Principalmente per il gusto di farlo, sto scrivendo un programma per scaricare i testi delle canzoni e salvarli su file musicali (ad esempio tag ID3v2). Ho già installato la maggior parte di questo: ho due classi che gestiscono il download (una per sito supportato dal programma), oltre al codice necessario per scrivere i tag sui file.
Il design che volevo fin dall'inizio era abbastanza semplice e sincrono; Avevo programmato di creare un'API sincrona / bloccante per scaricare testi e avviare tali attività in background per mantenere viva l'interfaccia utente.
L'idea era che la classe che desidera recuperare i testi creerebbe un'istanza LyricFetcher
e chiamerà string LyricFetcher::fetchLyrics(string artist, string title)
. Questo, a sua volta, cerca di recuperare i testi di ciascun sito in ordine, usando qualcosa come
for (Site *site : sites) {
lyrics = site->fetchLyrics(artist, title);
if (successfully downloaded lyrics)
return lyrics;
}
return (didn't find anything);
I problemi si sono manifestati quando ho capito che le uniche API che potevo utilizzare per recuperare i dati da Internet erano asincrone; avresti impostato una richiesta e dirla (tramite segnali e slot) per dirti quando è finita.
Con quello, non potevo semplicemente restituire i testi come una stringa.
Come soluzione alternativa, ho progettato l'API per utilizzare un callback, che il codice specifico del sito chiama quando ha terminato il download o non è riuscito, ad esempio qualcosa come void LyricFetcher::fetchLyrics(string artist, string title, function<void(string)> callback)
.
Il callback è stato quindi inoltrato alle classi specifiche del sito, che hanno lo stesso tipo di firma del fetchLyrics
sopra.
Questo ha funzionato bene per un singolo sito, ma mi sono imbattuto in nuovi problemi cercando di implementare lo pseudocodice multi-sito sopra.
Non riesco a utilizzare lo pseudocodice, poiché site->fetchLyrics
restituirebbe immediatamente.
L'unica "soluzione" che ho trovato era usare callback annidati, che è orribile.
void LyricFetcher::fetchLyrics(string artist, string title, function<void(string)> callback) {
site1->fetchLyrics(artist, title, [=](string lyrics) {
if (did match)
callback(lyrics);
else {
site2->fetchLyrics(artist, title, [=](string lyrics) {
...
Non solo è brutto, ma significa anche che non puoi scorrere i siti in un ciclo semplice. (Non sono nemmeno sicuro che funzionerebbe affatto.)
Quale è un buon modo di progettare in merito a problemi come questi?
L'API "user facing" di LyricFetcher::fetchLyrics
dovrebbe preferibilmente essere sincrona (con i testi come un semplice valore di ritorno), o usare segnali / slot per segnalare il completamento (e trasferire i risultati).
FWIW, questo è in C ++ con Qt (C ++ 14, Qt 5.6), ma ho cercato di rimanere con uno pseudocodice C ++ y nel post.