La gestione dei thread in un'applicazione asincrona è una questione delicata. Spesso, quando si cerca di eseguire un lavoro in background, si tende a considerare l'idea di iniziare l'esecuzione in un thread separato. Tuttavia, questa non è sempre la scelta migliore, in particolare quando si tratta di applicazioni web. In questi casi, l'uso del pool di thread non offre alcun vantaggio significativo, e l'unico obiettivo da ottimizzare dovrebbe essere il numero totale di thread utilizzati. Per semplificare le cose, Task.Run è una chiamata molto semplice da eseguire, ma è meglio lasciare che sia il chiamante dell'API a decidere quando e come utilizzarla, in base alle proprie esigenze specifiche.
Quando si sviluppano API asincrone, una pratica comune è quella di esporre un Task Asynchronous Pattern (TAP), che rappresenta un modello semplice e facilmente comprensibile per l'esecuzione di operazioni asincrone. Questo modello è particolarmente utile quando si desidera integrare un'operazione di lunga durata nel flusso del programma. Ma cosa succede quando l'operazione asincrona non è già disponibile come un'API TAP, oppure quando non stiamo consumando un'API, ma stiamo gestendo direttamente un'operazione asincrona manualmente? In questi casi, uno degli strumenti principali che possiamo usare è TaskCompletionSource.
Il TaskCompletionSource è un meccanismo che permette di creare un Task "puppet" (marionetta), ovvero un task che possiamo manipolare a piacere, facendolo completare o fallire in un momento preciso, a seconda delle necessità. La bellezza di questo approccio è che permette di combinare la flessibilità del modello asincrono con il controllo diretto sulle operazioni che devono essere eseguite. Vediamo un esempio pratico per chiarire meglio il concetto.
Immagina di voler creare un metodo che incapsula una finestra di dialogo che chiede all'utente di concedere un permesso per continuare. Questo tipo di operazione non è direttamente legata a una chiamata di rete o a un'operazione a lunga durata, ma piuttosto riguarda un'interazione utente che può richiedere un tempo indefinito per essere completata. Il metodo ideale per gestire questa situazione è un metodo asincrono, poiché vuoi liberare il thread dell'interfaccia utente per permettere all'utente di interagire con la finestra di dialogo.
Ecco come potrebbe apparire il codice che gestisce questa situazione:
Nel codice sopra, il metodo GetUserPermission() non è contrassegnato come async, poiché stiamo creando manualmente un Task invece di delegarlo al compilatore. Il TaskCompletionSource è utilizzato per creare il Task e renderlo disponibile come proprietà da restituire. Successivamente, possiamo usare il metodo SetResult per segnare il completamento del Task, a seconda dell'interazione dell'utente con la finestra di dialogo.
L'importante, qui, è che il chiamante del metodo può utilizzare il Task come se fosse un'operazione asincrona standard, semplicemente aspettando il risultato tramite await. Ad esempio:
Questo approccio offre una gestione pulita ed efficace dei Task asincroni, senza dover reinventare la ruota ogni volta che si desidera gestire una finestra di dialogo o un'altra operazione che può durare un tempo indefinito.
Una limitazione da notare è che non esiste una versione non generica di TaskCompletionSource. Tuttavia, poiché Task è una sottoclasse di Task<T>, è possibile utilizzare un Task ovunque ci si aspetti un Task. In altre parole, è possibile usare un TaskCompletionSource<T> per restituire un Task, anche se il tipo restituito è generico.
Questa strategia è utile non solo per operazioni di dialogo, ma per ogni situazione in cui è necessario ottenere un risultato asincrono da un'operazione che non è parte di un'API già esistente. Dalla richiesta di una risorsa fino alla gestione di eventi complessi, la creazione di un Task puppetta consente di ottenere il massimo controllo sull'esecuzione asincrona, mantenendo comunque un'interfaccia semplice e pulita per chi consuma l'API.
Endtext
Come funziona il flusso di esecuzione del codice asincrono nelle applicazioni?
Nel contesto delle applicazioni asincrone, l’esecuzione del codice può sembrare complessa, ma in realtà è una questione di gestione dei thread e dei contesti di sincronizzazione. Una delle chiavi per comprendere come funziona l'asincrono è sapere quale thread esegue il codice e come il controllo passa da un thread all'altro. Il comportamento del codice asincrono dipende dalla sua posizione nell'applicazione, che può essere una UI o un'applicazione web, e dal modo in cui le operazioni asincrone sono orchestrate.
In un’applicazione UI, ad esempio, il codice che viene eseguito prima del primo await viene gestito dal thread principale, che è il thread della UI. In un'applicazione web come ASP.NET, invece, il codice viene eseguito su un thread del pool di lavoro di ASP.NET. Questo significa che, quando viene invocato un altro metodo asincrono all'interno di una funzione, tale espressione deve essere eseguita sul thread chiamante, che continuerà a eseguire il codice fino a che non raggiunge un metodo che effettivamente restituisce un Task.
In generale, il codice che precede il primo punto di asincronia in un’applicazione UI è tutto eseguito dal thread della UI, e ciò può influire sulla reattività dell’interfaccia utente. Questo codice potrebbe richiedere un tempo considerevole per l’esecuzione, quindi se non gestito correttamente, potrebbe far sembrare l’interfaccia lenta o non responsiva. Non basta utilizzare async per garantire che l’interfaccia resti reattiva: è fondamentale monitorare le performance e scoprire dove effettivamente vengono spesi i tempi, ad esempio utilizzando un profiler delle prestazioni.
In un’operazione asincrona, la domanda fondamentale è: quale thread gestisce effettivamente l'operazione asincrona? La risposta, per molte operazioni tipiche come le richieste di rete, è che non ci sono thread bloccati in attesa che l'operazione venga completata. Quando si utilizza Task.Run per eseguire un calcolo, per esempio, un thread del pool di thread è impegnato, ma nel caso delle operazioni I/O, come le richieste di rete, viene utilizzato un thread condiviso chiamato "IO completion port" su Windows. Quando una richiesta di rete è completata, un gestore di interruzioni nel sistema operativo inserisce il lavoro in una coda per l’IO completion port. In pratica, le richieste di rete non bloccano thread individuali, ma vengono gestite da un numero ridotto di thread che processano tutte le risposte in arrivo.
Un concetto importante per comprendere il comportamento delle operazioni asincrone in .NET è il SynchronizationContext. Questa classe consente di eseguire codice su un tipo specifico di thread, come quello della UI. La maggior parte dei contesti di sincronizzazione implementa una sottoclasse di SynchronizationContext, che si occupa di garantire che il codice venga eseguito nel contesto corretto. Ogni thread ha un proprio SynchronizationContext, che può essere acquisito e utilizzato per eseguire codice in quel contesto in futuro. La caratteristica fondamentale di SynchronizationContext è che permette di posticipare l'esecuzione di un delegato in un contesto specifico, senza dover conoscere esattamente quale thread è stato utilizzato inizialmente.
Quando si utilizza await, la funzione in sospeso cattura il contesto di sincronizzazione corrente, ed è questo contesto che viene utilizzato per riprendere l'esecuzione della funzione dopo il completamento dell'operazione asincrona. Questo meccanismo semplifica la programmazione, poiché l’implementazione di await si occupa di garantire che il codice venga eseguito sullo stesso thread di chiamata, mantenendo la coerenza nel comportamento del programma. Tuttavia, ci sono delle eccezioni. Se il contesto di sincronizzazione è uno che ha più thread disponibili, come nel caso del thread pool, o se il contesto non implica effettivamente un cambio di thread, il codice potrebbe essere ripreso su un thread diverso da quello di partenza.
Questo comportamento non si applica solo alle operazioni asincrone classiche ma anche alle interfacce utente. Infatti, nelle applicazioni UI, è fondamentale che il codice venga ripreso sullo stesso thread per continuare a manipolare l'interfaccia utente senza causare anomalie. Il metodo di ripristino su un thread specifico grazie al SynchronizationContext è particolarmente utile nelle applicazioni WPF e WinForms, dove l’interfaccia utente deve rimanere interattiva.
Un esempio pratico di questo flusso di esecuzione può essere illustrato con un semplice caso in cui si fa clic su un pulsante per scaricare un'icona (favicon). Il ciclo di vita dell'operazione asincrona si può riassumere nei seguenti passaggi: il thread della UI esegue il codice fino al primo await, quindi l'operazione di download viene avviata senza bloccare il thread della UI, che può continuare a rispondere ad altri eventi. Il thread di completamento dell'I/O si occupa di gestire la risposta quando arriva, quindi il codice continua sul thread della UI, che riprende l’esecuzione dal punto sospeso. Questo processo di gestione asincrona permette di eseguire operazioni costose senza bloccare l’interfaccia utente, migliorando significativamente l’esperienza dell’utente.
Quando si progettano applicazioni asincrone, è fondamentale capire come la gestione dei thread e dei contesti di sincronizzazione possa influire sulla reattività dell'applicazione e sulla correttezza dell’esecuzione del codice. È importante anche ricordare che, sebbene l’uso di async e await semplifichi la scrittura del codice asincrono, non elimina automaticamente il rischio di inefficienze o di malfunzionamenti, e che il monitoraggio delle performance può essere cruciale per ottimizzare l'esperienza dell'utente.
Come gestire il contesto di sincronizzazione e le eccezioni nel codice asincrono
Il concetto di SynchronizationContext in C# è fondamentale per comprendere come il framework gestisce il threading e l'esecuzione asincrona. Nonostante gli oggetti SynchronizationContext siano equivalenti, questa struttura fa credere al Task Parallel Library (TPL) che sia necessario inviare nuovamente un messaggio al thread principale per la continuazione dell'esecuzione del codice. Quando il codice asincrono riprende l'esecuzione dopo un await, è possibile osservare che ogni singola linea del codice viene eseguita dal thread principale. Questo processo non è immediatamente evidente, ma può essere utile per comprendere come funziona il framework di C#.
Nel caso in cui il SynchronizationContext catturato durante l'esecuzione di un Task sia lo stesso del contesto attuale, .NET evita l'operazione di Post, un'operazione relativamente costosa. Tuttavia, quando i contesti sono differenti, .NET esegue un Post costoso per garantire che il Task venga ripreso nel contesto appropriato. Questa differenza è importante quando si lavora con codice ad alte prestazioni o con librerie dove non è rilevante su quale thread venga eseguito il codice. In queste situazioni, è possibile evitare il costo del Post utilizzando ConfigureAwait(false) prima di attendere il Task.
Il metodo ConfigureAwait(false) è un suggerimento per .NET che non importa su quale thread venga ripreso il codice. Tuttavia, il suo comportamento dipende dal tipo di thread che ha completato il Task: se si tratta di un thread del pool di thread, il codice continuerà su di esso, mentre se il thread è considerato importante (ad esempio il thread UI), .NET preferirà liberarlo per altre operazioni, facendo proseguire il codice su un thread del pool.
Un aspetto importante da considerare riguarda l'interazione con il codice sincrono quando si lavora con codice asincrono. È possibile consumare codice sincrono da un metodo asincrono semplicemente eseguendolo su un thread del pool di thread utilizzando Task.Run e attendendo il risultato con await. Tuttavia, quando si cerca di consumare codice asincrono da un metodo sincrono, possono sorgere dei problemi nascosti. Ad esempio, l'uso della proprietà Result di un Task in un contesto sincrono può portare a bloccare il thread chiamante, il che in alcuni casi porta a deadlock, soprattutto quando il codice viene eseguito sul thread UI. In tal caso, il thread UI rimarrà bloccato in attesa del completamento del Task, mentre il Task stesso cercherà di riprendere l'esecuzione su di esso, creando un'impasse.
Per evitare questo problema, è possibile eseguire il codice asincrono nel pool di thread prima di invocarlo, in modo che il contesto di sincronizzazione catturato sia quello del pool di thread, evitando il deadlock. Tuttavia, questa soluzione è poco elegante e spesso è preferibile rendere il codice chiamante asincrono, in modo da evitare situazioni di blocco e migliorare la gestione del flusso di lavoro.
Un altro aspetto critico nella gestione del codice asincrono riguarda le eccezioni. In un codice sincrono, le eccezioni vengono propagate verso l'alto nella call stack finché non vengono catturate da un blocco try..catch. Nel codice asincrono, tuttavia, la call stack che si vede dopo il ripristino del metodo asincrono ha poco a che fare con l'intento del programmatore e contiene principalmente logica del framework per riprendere l'esecuzione. C# ha modificato il comportamento delle eccezioni in questi scenari per renderle più utili e facili da diagnosticare.
Quando un metodo asincrono genera un'eccezione, questa viene catturata dal Task che è stato restituito al chiamante e viene registrata come un'eccezione "faulted". Se un metodo è in attesa di un Task che fallisce, l'esecuzione del metodo viene ripresa lanciando l'eccezione. La caratteristica interessante di questa gestione è che l'eccezione mantiene il suo stack trace originale e continua a raccogliere informazioni mentre risale la call stack. In questo modo, anche se un'eccezione viene rilanciata tramite un await, il stack trace rifletterà correttamente il percorso che ha portato all'errore, facilitando il debug e la risoluzione del problema.
Tuttavia, c'è un'ulteriore difficoltà quando si utilizzano catene di metodi asincroni: il metodo che lancia un'eccezione potrebbe trovarsi in una posizione più profonda nella call stack rispetto a quello che effettivamente rileva l'errore. Questo non impedisce la corretta gestione dell'eccezione, ma può complicare l'interpretazione del flusso di esecuzione. Per esempio, un'eccezione lanciata in un metodo asincrono potrebbe arrivare fino al metodo che ha invocato il Task, ma nel frattempo il framework aggiunge il proprio stack trace tra il punto in cui è stata lanciata l'eccezione e il punto in cui viene catturata.
In generale, è importante prestare attenzione al comportamento di ConfigureAwait(false) e a come le eccezioni vengono propagate nei metodi asincroni. La corretta comprensione di questi concetti è essenziale per evitare deadlock e gestire efficacemente le eccezioni, rendendo il codice asincrono non solo più performante, ma anche più robusto.
Come Gestire le Eccezioni nel Codice Asincrono
Una delle differenze principali tra il codice asincrono e quello sincrono riguarda il punto in cui un'eccezione generata da un metodo chiamato viene effettivamente sollevata. Nei metodi asincroni, l'eccezione viene sollevata durante l'await, piuttosto che nel momento in cui il metodo viene chiamato. Questo comportamento diventa evidente se si separano la chiamata al metodo e l'operazione di await.
Un errore comune, soprattutto quando si chiamano metodi asincroni che non restituiscono valori, è dimenticarsi di usare l'await. In questo caso, si rischia di ottenere un comportamento simile a un blocco catch vuoto che intercetta tutte le eccezioni senza gestirle. Tale pratica può causare uno stato del programma non valido e bug sottili che si verificano lontano dalla causa. È fondamentale, quindi, attendere sempre qualsiasi metodo asincrono per evitare di complicare inutilmente il debug del codice.
Va inoltre notato che, con l'introduzione del codice asincrono in .NET, il comportamento delle eccezioni è cambiato. Fino alla versione precedente di .NET, le eccezioni derivanti dal codice Task Parallel Library venivano rilanciate nel thread del finalizzatore, ma ciò non accade più nelle versioni successive a .NET 4.5.
Un altro aspetto critico riguarda i metodi asincroni che restituiscono void. Poiché non è possibile usare await su metodi di questo tipo, la gestione delle eccezioni deve essere diversa. Quando un'eccezione viene sollevata da un metodo asincrono che restituisce void, essa viene rilanciata nel thread chiamante. Se il metodo è stato chiamato in un contesto con un SynchronizationContext, l'eccezione verrà "posta" su di esso; in caso contrario, verrà sollevata nel thread pool. In entrambi i casi, è probabile che l'esecuzione dell'applicazione termini, a meno che non sia stato configurato un gestore di eccezioni non gestite. Per evitare tali scenari problematici, si consiglia di usare metodi async void solo quando si interagisce con codice esterno o quando si è certi che non verranno sollevate eccezioni.
In casi rari, dove non si è interessati a sapere se un metodo ha avuto successo e l'attesa del suo completamento risulta complessa, si può considerare di restituire un Task. In questo caso, sarebbe opportuno passare quel Task a un metodo che gestisce le eccezioni in modo adeguato. Una strategia utile è utilizzare un metodo di estensione che gestisca in sicurezza le eccezioni, come il seguente esempio:
Dove HandleException è un metodo che registra qualsiasi eccezione in un sistema di logging, come illustrato nel capitolo precedente.
Nel mondo asincrono, è anche necessario affrontare una situazione che non era possibile nel contesto sincrono: un metodo può sollevare più eccezioni contemporaneamente. Questo può accadere, ad esempio, quando si usa Task.WhenAll per attendere il completamento di un gruppo di operazioni asincrone. Se molte di queste operazioni falliscono, non vi è un'unica eccezione primaria; tutte devono essere gestite. WhenAll è uno dei meccanismi più comuni per produrre eccezioni multiple, ma ci sono numerosi altri modi per eseguire operazioni concorrenti utilizzando l'asincronia.
Per gestire queste eccezioni multiple, Task è stato progettato per contenere un oggetto AggregateException quando si verifica un errore. Quest'ultimo contiene una raccolta di eccezioni. Quando un'eccezione sfugge a un metodo asincrono, viene creata un'AggregateException con l'eccezione originale come eccezione interna, e successivamente questa viene aggiunta al Task. Così, quando si usa await per rilanciare l'eccezione, verrà sollevata solo la prima eccezione interna dell'AggregateException. Tuttavia, è possibile accedere all'oggetto Task direttamente per ottenere l'AggregateException completa, con tutte le eccezioni interne, e gestirle adeguatamente:
Per quanto riguarda la gestione delle eccezioni in un contesto asincrono, la raccomandazione TAP (Task Asynchronous Pattern) consente di sollevare eccezioni in modo sincrono, ma solo quando l'eccezione indica un errore nel chiamare il metodo, piuttosto che durante l'esecuzione del metodo stesso. Ad esempio, se un errore di chiamata deve essere rilevato prima di avviare un'operazione asincrona, è possibile farlo sincronamente in un metodo pre-esecuzione:
Questo approccio semplifica leggermente le tracce dello stack e aiuta nella comprensione degli errori, anche se l'efficacia di tale pratica dipende dal contesto.
Infine, va ricordato che l'uso del blocco finally in metodi asincroni funziona come ci si aspetta. Qualsiasi codice nel blocco finally sarà eseguito prima che l'esecuzione esca dal metodo contenente il blocco, che sia per un flusso normale o a causa di un'eccezione. Tuttavia, c'è una peculiarità nei metodi asincroni: non c'è alcuna garanzia che l'esecuzione esca mai dal metodo. Un metodo asincrono può facilmente raggiungere un await, fermarsi e poi essere dimenticato, venendo quindi raccolto dal garbage collector senza mai eseguire il blocco finally:
In conclusione, quando si progettano metodi asincroni, è cruciale prestare attenzione alla gestione delle eccezioni per evitare complicazioni e comportamenti imprevisti. La corretta gestione di await, la gestione dei metodi async void, e l'uso consapevole di AggregateException sono fondamentali per garantire che il codice asincrono sia robusto e sicuro.
Quali sono i contesti di sincronizzazione e perché sono rilevanti in C#?
Nel linguaggio C#, quando si lavora con metodi asincroni e si utilizza la parola chiave await, un aspetto fondamentale da comprendere è il concetto di "contesto di sincronizzazione". Questo contesto è ciò che permette di riprendere l'esecuzione di un metodo su un particolare tipo di thread, tra le altre cose. La gestione di tali contesti diventa particolarmente cruciale in applicazioni con interfacce utente (UI), in cui solo il thread principale può manipolare la UI. Questo aspetto è determinante perché, senza un'adeguata sincronizzazione, l'interfaccia utente potrebbe risultare inaccessibile o incoerente, compromettendo l'esperienza dell'utente.
Il concetto di contesto di sincronizzazione è complesso e viene trattato in modo più approfondito nel Capitolo 8. Tuttavia, è utile sapere che diversi tipi di contesto vengono "catturati" dal thread che invoca un metodo asincrono, e questi contesti vengono restaurati quando il metodo asincrono riprende l'esecuzione. Alcuni dei contesti più rilevanti includono il ExecutionContext, che è il contesto principale, il SecurityContext, in cui viene immagazzinata qualsiasi informazione di sicurezza associata al thread, e il CallContext, che permette di memorizzare dati personalizzati durante l'esecuzione di un thread logico. Inoltre, esistono altre variabili come LogicalCallContext, che si estende oltre i confini del singolo dominio di applicazione.
Quando si utilizza un metodo asincrono, è importante sapere che, sebbene il contesto venga ripristinato, tale operazione ha un costo. In particolare, l'uso di funzionalità che gestiscono il contesto, come l'impersonificazione in un contesto di sicurezza, potrebbe rallentare notevolmente l'esecuzione di un programma che fa ampio uso di metodi asincroni. Pertanto, è opportuno evitare, ove possibile, l'uso di funzionalità che creano contesti, a meno che non siano realmente necessarie per il corretto funzionamento dell'applicazione.
Un'altra situazione in cui è cruciale prestare attenzione riguarda l'uso della parola chiave await in specifici blocchi di codice, come i blocchi catch e finally. Sebbene sia perfettamente valido utilizzare await in un blocco try, non è consentito farlo in un blocco catch o finally. La ragione di questo divieto risiede nel fatto che, in un blocco catch, l'eccezione potrebbe essere ancora in fase di "unwinding" dello stack, e l'uso di await altererebbe la gestione del flusso delle eccezioni, rendendo il comportamento del programma imprecisabile. Per evitare tali problematiche, è possibile spostare l'uso di await al di fuori del blocco catch e gestire l'eventuale errore in un altro modo, ad esempio utilizzando una variabile booleana per tracciare se un'operazione ha generato un'eccezione.
Un altro caso in cui non è possibile usare await è nei blocchi di codice lock. Un blocco lock viene utilizzato per evitare che più thread accedano simultaneamente agli stessi oggetti. Tuttavia, nel contesto di codice asincrono, il thread che esegue un'operazione asincrona viene rilasciato prima che questa venga completata, e potrebbe quindi essere riutilizzato da un altro thread. Pertanto, mantenere un blocco lock durante l'uso di await potrebbe portare a conflitti e deadlock. In queste situazioni, è consigliabile utilizzare una forma di sincronizzazione esplicita, scrivendo codice che gestisca i blocchi in maniera separata prima e dopo l'operazione asincrona.
Allo stesso modo, le espressioni di query LINQ in C# non possono contenere direttamente await, poiché la sintassi per applicare la parola chiave async alle espressioni implicite generate dal compilatore non è supportata. Per aggirare questa limitazione, si può riscrivere la query utilizzando i metodi di estensione di LINQ e dichiarando esplicitamente il tipo di operazione asincrona con async all'interno della lambda.
Infine, l'uso di codice "unsafe" – un tipo di codice che consente di manipolare direttamente la memoria in modo non sicuro – non è compatibile con await. Il codice unsafe dovrebbe essere raro e limitato a metodi autonomi che non necessitano di operazioni asincrone. La trasformazione del codice per l'uso di await potrebbe compromettere la funzionalità di codice che non è stato progettato per l'asincronia, quindi è meglio evitare di combinare questi due concetti.
Per quanto riguarda la gestione delle eccezioni nei metodi asincroni, esse funzionano in modo simile a quelle nei metodi sincroni, ma con alcune differenze sottili. Quando un'operazione asincrona termina, il tipo Task restituisce una proprietà chiamata IsFaulted, che indica se l'operazione ha avuto esito negativo. In caso di errore, l'eccezione viene ripristinata e reimpostata, ma con il supporto di una nuova classe in .NET 4.5 chiamata ExceptionDispatchInfo, che permette di mantenere correttamente la traccia dello stack quando l'eccezione viene rilanciata. In altre parole, l'asincronicità non compromette la gestione delle eccezioni, che viene trattata in modo simile a quanto avviene nei metodi sincroni.
Tuttavia, è importante essere consapevoli di come l'eccezione venga propagata attraverso i metodi asincroni. Se un'eccezione viene sollevata durante l'esecuzione di un metodo asincrono e non viene catturata al suo interno, essa viene immagazzinata nel Task e rilanciata nel punto in cui si trova il chiamante. Questa propagazione delle eccezioni avviene con lo stesso flusso delle eccezioni nei metodi sincroni, creando una "stack trace" virtuale che consente di tracciare l'origine dell'errore.
Come possiamo rappresentare costruttivamente il quantificatore esistenziale in Lean?
Qual è l'importanza delle nuove tecnologie e tecniche chirurgiche nel trattamento dei meningiomi parasagittali?
Come rimuovere facilmente lo sfondo da un'immagine: tecniche e strumenti
Come risolvere problemi con la trasmissione di calore e vibrazioni in geometrie sferiche e cilindriche: l'approccio delle trasformate di Fourier
Perché gli atteggiamenti razziali spingono gli evangelici bianchi verso il voto conservatore

Deutsch
Francais
Nederlands
Svenska
Norsk
Dansk
Suomi
Espanol
Italiano
Portugues
Magyar
Polski
Cestina
Русский