Hai fatto diverse domande nella tua domanda. Li analizzerò in modo leggermente diverso rispetto a te. Ma prima lasciami rispondere direttamente alla domanda.
Vogliamo tutti una fotocamera che sia leggera, di alta qualità ed economica, ma come dice il proverbio, puoi ottenere solo un massimo di due su quei tre. Sei nella stessa situazione qui. Volete una soluzione che sia efficiente, sicura e condivida il codice tra i percorsi sincroni e asincroni. Ne otterrai solo due.
Lasciami analizzare perché. Inizieremo con questa domanda:
Ho visto che la gente dice che potresti usare GetAwaiter().GetResult()
un metodo asincrono e invocarlo dal tuo metodo di sincronizzazione? Quel thread è sicuro in tutti gli scenari?
Il punto di questa domanda è "posso condividere i percorsi sincroni e asincroni facendo in modo che il percorso sincrono faccia semplicemente un'attesa sincrona sulla versione asincrona?"
Vorrei essere super chiaro su questo punto perché è importante:
DOVREI FERMARE IMMEDIATAMENTE DI PRENDERE QUALSIASI CONSIGLIO DA QUESTE PERSONE .
Questo è un consiglio estremamente negativo. È molto pericoloso recuperare un risultato in modo sincrono da un'attività asincrona a meno che non si abbia la prova che l'attività è stata completata normalmente o in modo anomalo .
Il motivo per cui questo è un consiglio estremamente negativo è, beh, considerare questo scenario. Volete falciare il prato, ma la lama del tosaerba è rotta. Decidi di seguire questo flusso di lavoro:
- Ordina un nuovo blade da un sito web. Questa è un'operazione asincrona a latenza elevata.
- Attendi in modo sincrono, ovvero dormi fino a quando non hai il blade in mano .
- Controllare periodicamente la cassetta postale per vedere se il blade è arrivato.
- Rimuovere la lama dalla scatola. Ora ce l'hai in mano.
- Installare la lama nel tosaerba.
- Falciare il prato.
Che succede? Dormi per sempre perché l'operazione di controllo della posta è ora bloccata su qualcosa che accade dopo l'arrivo della posta .
È estremamente facile entrare in questa situazione quando si attende in modo sincrono un'attività arbitraria. Quell'attività potrebbe aver programmato il lavoro in futuro del thread che è ora in attesa , e ora quel futuro non arriverà mai perché lo stai aspettando.
Se fai un'attesa asincrona, allora va tutto bene! Controlli periodicamente la posta e, mentre aspetti, fai un sandwich o fai le tue tasse o altro; continui a lavorare mentre aspetti.
Non attendere mai in modo sincrono. Se l'attività viene eseguita, non è necessario . Se l'attività non viene eseguita ma pianificata per l'esecuzione del thread corrente, è inefficiente perché il thread corrente potrebbe servire altri lavori invece di attendere. Se l'attività non è fatto ed eseguire programma sul thread corrente, è appeso ad aspettare in modo sincrono. Non vi è alcuna buona ragione per attendere in modo sincrono, di nuovo, a meno che non si sappia già che l'attività è completa .
Per ulteriori informazioni su questo argomento, vedere
https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
Stephen spiega lo scenario del mondo reale molto meglio di me.
Consideriamo ora "l'altra direzione". Possiamo condividere il codice rendendo la versione asincrona semplicemente fare la versione sincrona su un thread di lavoro?
Questa è probabilmente e probabilmente una cattiva idea, per i seguenti motivi.
È inefficiente se l'operazione sincrona è un lavoro IO ad alta latenza. Questo essenzialmente assume un lavoratore e lo fa dormire fino a quando non viene eseguita un'attività. Le discussioni sono follemente costose . Consumano un milione di byte di spazio minimo per impostazione predefinita, richiedono tempo, prendono risorse del sistema operativo; non vuoi bruciare un thread facendo un lavoro inutile.
L'operazione sincrona potrebbe non essere scritta come thread-safe.
Questa è una tecnica più ragionevole se il lavoro ad alta latenza è associato al processore, ma in tal caso probabilmente non si desidera semplicemente passarlo a un thread di lavoro. Probabilmente si desidera utilizzare la libreria di attività parallele per parallelizzare il maggior numero possibile di CPU, probabilmente si desidera la logica di annullamento e non si può semplicemente fare in modo che la versione sincrona faccia tutto ciò, perché sarebbe già la versione asincrona .
Ulteriori letture; ancora una volta, Stephen lo spiega molto chiaramente:
Perché non usare Task.Run:
https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-using.html
Altri scenari "fai e non fare" per Task.Run:
https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html
Cosa ci lascia quindi? Entrambe le tecniche per la condivisione del codice portano a deadlock o grandi inefficienze. La conclusione che giungiamo è che devi fare una scelta. Vuoi un programma che sia efficiente e corretto e che rallegri il chiamante o vuoi salvare alcune sequenze di tasti comportando la duplicazione di una piccola quantità di codice tra i percorsi sincrono e asincrono? Non hai entrambe le cose, temo.