Comment mettre en œuvre Android rappels en C# à l'aide de async/await avec Xamarin ou Dot42?
Comment mettre en place des rappels en C# à l'aide de async/await avec Xamarin pour Android? Et comment cela se compare à la norme de programmation Java pour Android?
Avec Xamarin pour Android de la version 4.7, au moment de cette écriture toujours en version bêta accessible au public, nous pouvons utiliser .NET 4.5 fonctionnalités à mettre en œuvre "asynchrone" méthodes et "attendent" les appels à eux. Il m'a toujours agacé, que si un rappel est nécessaire en Java, la logique de flux de code dans une fonction est interrompue, vous devez continuer le code dans la fonction suivante lorsque la fonction de rappel retourne. Considérons ce scénario:
Je veux rassembler une liste de tous les moteurs de synthèse vocale sur un appareil Android, puis demandez à chacun d'eux les langues qu'il a installé. Le petit “TTS " Setup” de l'activité que j'ai écrit, présente à l'utilisateur de deux cases (“spinners”), une liste de toutes les langues que tous les moteurs TTS sur ce dispositif de soutien. Les autres encadré ci-dessous répertorie toutes les voix disponibles pour la langue sélectionnée dans la première zone, de nouveau disponibles TTS les moteurs.
Idéalement, l'initialisation de cette activité qui devrait se produire dans une fonction, par exemple, dans onCreate(). Pas possible avec la norme de programmation Java parce que:
Cela nécessite deux “perturbateur” rappels – d'abord initialiser le moteur TTS – il devient pleinement opérationnel que lorsque le onInit() est appelée en arrière. Ensuite, lorsque nous avons une initialisé TTS objet, nous avons besoin de l'envoyer à un “android.de la parole.tts.moteur.CHECK_TTS_DATA” l'intention, et de l'attendre en conséquence à nouveau dans notre activité de rappel onActivityResult(). Une autre perturbation de la logique de flux. Si nous sommes itérer sur une liste de TTS les moteurs, même le compteur de la boucle pour cette itération ne peut pas être une variable locale dans une fonction unique, mais doit être effectuée par un membre de classe privée à la place. Assez compliqué à mon avis.
Ci-dessous je vais essayer de décrire le nécessaire de code Java pour atteindre cet objectif.
Désordre code Java pour recueillir tous les moteurs TTS et la voix de leur soutien
publicclassVoiceSelector extends Activity{privateTextToSpeech myTts;privateint myEngineIndex;//loop counter when initializing TTS engines//Called from onCreate to colled all languages and voices from all TTS engines, initialize the spinnersprivatevoid getEnginesAndLangs(){
myTts =newTextToSpeech(AndyUtil.getAppContext(),null);List<EngineInfo> engines;
engines = myTts.getEngines();//at least we can get the list of engines without initializing myTts object…try{ myTts.shutdown();}catch(Exception e){};
myTts =null;
myEngineIndex =0;//Initialize the loop iterating through all TTS enginesif(engines.size()>0){for(EngineInfo ei : engines)
allEngines.add(newEngLang(ei));
myTts =newTextToSpeech(AndyUtil.getAppContext(), ttsInit, allEngines.get(myEngineIndex).name());//DISRUPTION 1: we can’t continue here, must wait until ttsInit callback returns, see below}}privateTextToSpeech.OnInitListener ttsInit =newTextToSpeech.OnInitListener(){@Overridepublicvoid onInit(int status){if(myEngineIndex < allEngines.size()){if(status ==TextToSpeech.SUCCESS){//Ask a TTS engine which voices it currently has installedEngLang el = allEngines.get(myEngineIndex);Intentin=newIntent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);in=in.setPackage(el.ei.name);//set engine package nametry{
startActivityForResult(in, LANG_REQUEST);//goes to onActivityResult()//DISRUPTION 2: we can’t continue here, must wait for onActivityResult()…}catch(Exception e){//ActivityNotFoundException, also got SecurityException from com.turboledif(myTts !=null)try{
myTts.shutdown();}catch(Exception ee){}if(++myEngineIndex < allEngines.size()){//If our loop was not finished and exception happened with one engine,//we need this call here to continue looping…
myTts =newTextToSpeech(AndyUtil.getAppContext(), ttsInit, allEngines.get(myEngineIndex).name());}else{
completeSetup();}}}}else
completeSetup();}@Overrideprotectedvoid onActivityResult(int requestCode,int resultCode,Intent data){if(requestCode == LANG_REQUEST){//We return here after sending ACTION_CHECK_TTS_DATA intent to a TTS engine//Get a list of voices supported by the given TTS engineif(data !=null){ArrayList<String> voices = data.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES);//… do something with this list to save it for later use}if(myTts !=null)try{
myTts.shutdown();}catch(Exception e){}if(++myEngineIndex < allEngines.size()){//and now, continue looping through engines list…
myTts =newTextToSpeech(AndyUtil.getAppContext(), ttsInit, allEngines.get(myEngineIndex).name());}else{
completeSetup();}}}
Notez que la ligne qui crée une nouvelle TTS objet avec ttsInit rappel, doit être répété 3 fois pour continuer la boucle à travers tous les moteurs disponibles en cas d'exceptions ou d'autres erreurs se produisent. Peut-être que le ci-dessus pourrait être écrit un peu mieux, par exemple, j'ai pensé que je pouvais créer une classe interne pour garder le code de boucle localisée et mon compteur de boucle pour au moins ne pas être un membre de la classe principale, mais il est toujours en désordre. Suggestion pour l'amélioration de ce code Java bienvenue.
Beaucoup plus propre solution: Xamarin C# avec les méthodes asynchrones
Tout d'abord, pour simplifier les choses, j'ai créé une classe de base pour mon Activité qui fournit CreateTtsAsync() pour éviter les PERTURBATIONS 1 dans le code Java ci-dessus, et StartActivityForResultAsync() pour éviter les PERTURBATIONS 2 méthodes.
//Base class for an activity to create an initialized TextToSpeech//object asynchronously, and starting intents for result asynchronously,//awaiting their result. Could be used for other purposes too, remove TTS//stuff if you only need StartActivityForResultAsync(), or add other//async operations in a similar manner.publicclassTtsAsyncActivity:Activity,TextToSpeech.IOnInitListener{protectedconstString TAG ="TtsSetup";privateint _requestWanted =0;privateTaskCompletionSource<Java.Lang.Object> _tcs;//Creates TTS object and waits until it's initialized. Returns initialized object,//or null if error.protected async Task<TextToSpeech>CreateTtsAsync(Context context,String engName){
_tcs =newTaskCompletionSource<Java.Lang.Object>();var tts =newTextToSpeech(context,this, engName);if((int)await _tcs.Task!=(int)OperationResult.Success){Log.Debug(TAG,"Engine: "+ engName +" failed to initialize.");
tts =null;}
_tcs =null;return tts;}//Starts activity for results and waits for this result. Calling function may//inspect _lastData private member to get this result, or null if any error.//For sure, it could be written better to avoid class-wide _lastData member...protected async Task<Intent>StartActivityForResultAsync(Intent intent,int requestCode){Intent data =null;try{
_tcs =newTaskCompletionSource<Java.Lang.Object>();
_requestWanted = requestCode;StartActivityForResult(intent, requestCode);//possible exceptions: ActivityNotFoundException, also got SecurityException from com.turboled
data =(Intent) await _tcs.Task;}catch(Exception e){Log.Debug(TAG,"StartActivityForResult() exception: "+ e);}
_tcs =null;return data;}protectedoverridevoidOnActivityResult(int requestCode,Result resultCode,Intent data){base.OnActivityResult(requestCode, resultCode, data);if(requestCode == _requestWanted){
_tcs.SetResult(data);}}voidTextToSpeech.IOnInitListener.OnInit(OperationResult status){Log.Debug(TAG,"OnInit() status = "+ status);
_tcs.SetResult(newJava.Lang.Integer((int)status));}}
Maintenant, je peux écrire tout le code en boucle à travers le TTS les moteurs et en leur demandant disponible langues et de voix à l'intérieur d'une fonction, d'éviter une boucle courent à travers les trois fonctions différentes:
//Method of public class TestVoiceAsync : TtsAsyncActivityprivate async voidGetEnginesAndLangsAsync(){
_tts =newTextToSpeech(this,null);IList<TextToSpeech.EngineInfo> engines = _tts.Engines;try{
_tts.Shutdown();}catch{/* don't care */}foreach(TextToSpeech.EngineInfo ei in engines){Log.Debug(TAG,"Trying to create TTS Engine: "+ ei.Name);
_tts = await CreateTtsAsync(this, ei.Name);//DISRUPTION 1 from Java code eliminated, we simply await TTS engine initialization here.if(_tts !=null){var el =newEngLang(ei);
_allEngines.Add(el);Log.Debug(TAG,"Engine: "+ ei.Name+" initialized correctly.");var intent =newIntent(TextToSpeech.Engine.ActionCheckTtsData);
intent = intent.SetPackage(el.Ei.Name);Intent data = await StartActivityForResultAsync(intent, LANG_REQUEST);//DISTRUPTION 2 from Java code eliminated, we simply await until the result returns.try{//don't care if lastData or voices comes out null, just catch exception and continueIList<String> voices = data.GetStringArrayListExtra(TextToSpeech.Engine.ExtraAvailableVoices);Log.Debug(TAG,"Listing voices for "+ el.Name()+" ("+ el.Label()+"):");foreach(String s in voices){
el.AddVoice(s);Log.Debug(TAG,"- "+ s);}}catch(Exception e){Log.Debug(TAG,"Engine "+ el.Name()+" listing voices exception: "+ e);}try{
_tts.Shutdown();}catch{/* don't care */}
_tts =null;}}//At this point we have all the data needed to initialize our language//and voice selector spinners, can complete the activity setup....}
Le projet Java et le C# du projet, à l'aide de Visual Studio 2012 avec Xamarin pour Android add-on, sont maintenant disponibles sur GitHub:
Apprendre à faire avec Xamarin pour Android essai gratuit est amusant, mais ça vaut le $$ pour Xamarin de licence, puis le poids de chaque APK vous créez pour le Google Play Store d'environ 5 MO en Mono runtimes nous avons à distribuer? Je souhaite que Google a fourni Mono machine virtuelle en tant que norme de la composante du système sur l'égalité des droits avec Java/Dalvik.
P. S. a Examiné le vote sur cet article, et je vois qu'il obtient également quelques voix. Suppose qu'ils doivent être à venir à partir de Java passionnés! 🙂 Encore une fois, des suggestions sur la façon d'améliorer mon code Java sont également les bienvenus.
Dot42 également mis en place, async/await' des mots-clés dans leurs C# de produit pour Android, et j'ai essayé le portage à ce projet de test. Ma première tentative a échoué avec un accident quelque part dans Dot42 bibliothèques, dans l'attente (de manière asynchrone, bien sûr 🙂 ) pour un correctif à partir d'eux, mais il est un fait intéressant, ils ont observé et mis en œuvre lorsqu'il s'agit de "asynchrone" appels à partir de l'Android de l'activité des gestionnaires d'événement:
Par défaut, s'il existe une activité "modification de la configuration" alors que vous êtes en attente d'un résultat d'une longue opération asynchrone à l'intérieur d'une activité de gestionnaire d'événements, comme par exemple, changement d'orientation, l'activité est détruit et recréé par le système. Si après un tel changement, vous le retour d'une "asynchrone" opération au moyen d'un code de gestionnaire d'événements, le " il " de l'objet de l'activité n'est plus valide, et si vous avez stocké un objet en pointant des contrôles au sein de cette activité, ils sont pas valides (ils point à l'ancienne, aujourd'hui détruit des objets).
J'ai frappé ce problème dans mon code de production (en Java) et a travaillé autour de par la configuration de l'activité pour en être informé, et n'est pas détruit et recréé sur de tels événements. Dot42 est venu avec une autre solution, tout à fait intéressant:
var data = await webClient
.DownloadDataTaskAsync(myImageUrl).ConfigureAwait(this);
L' .configureAwait(ce) extension (plus un plus de ligne de code dans l'activité OnCreate() pour configurer des choses) veille à ce que votre 'ce' objet est encore valide, les points à l'instance actuelle de l'activité, lorsque vous revenez de l'attendent, même si le changement de configuration. Je pense que c'est bon pour au moins être conscients de cette difficulté, lorsque vous commencez à utiliser async/await Android avec le code de l'INTERFACE utilisateur, voir plus writeup sur ce à Dot42 blog: http://blog.dot42.com/2013/08/how-we-implemented-asyncawait.html?showComment=1377758029972#c6022797613553604525
Mise à jour sur Dot42 crash
Async/await crash j'ai vécu est maintenant fixé dans Dot42, et il fonctionne très bien. En fait, mieux que Xamarin du code de la puce de la manipulation de 'cette' objet dans Dot42 entre l'activité de la destruction/de loisirs des cycles. L'ensemble de mon code C# ci-dessus doivent être mis à jour pour tenir compte de ces cycles, et actuellement il n'est pas possible de Xamarin, seulement dans Dot42. Je vais mettre à jour ce code sur la demande d'autres membres, pour l'instant, il semble que cet article n'est pas d'obtenir beaucoup d'attention.
await Task.Run(delegate { Event1.WaitOne(); }); Je pense que c'est vraiment laid. Une bien meilleure solution serait d'utiliser TaskCompletionSource. Et tous ceux qui vide catches aussi ne sont pas une bonne pratique. Génial, merci, @svick! Expérimenter avec TaskCompetionSource et d'améliorer mon code, alors que mon libre Xamarin procès dure. Je suis encore à apprendre le C#, Java et Android, donc tous les commentaires m'aider. Vide captures - dans certains endroits, je n'ai vraiment pas de soins si un buggy moteur TTS, une 3ème partie du code après tout, les accidents, et encore envie de continuer ma boucle. Mais vous avez raison, il sera bon pour au moins la sortie de l'exception à la journal de Débogage pour avoir une idée de ce qui se passe. souhaiterait un rapide exemple de code montrant comment je pourrais utiliser TaskCompletionSource dans le cadre de mon application, en attente pour les rappels. Notez que nous ne pouvons pas bloquer le thread en cours lors de l'attente pour les rappels de retour. Merci! Il suffit de regarder la documentation pour les tc. C'est honnêtement une classe très simple; il suffit de regarder l'API est généralement suffisant pour comprendre comment l'utiliser. Vous utilisez le TCS classe pour créer une tâche que représente lorsque l'événement est prêt, mais sans bloquer un thread. Ensuite, vous pouvez await cette tâche. Alors plutôt que de fixer une remise à zéro automatique de l'événement que vous venez de définir le résultat du TCS. C'est ce que svick suggère. C'est le moyen le plus approprié de la signalisation à l'aide de la Task modèle. À l'aide d'un auto-réinitialisation de l'événement est conçu pour les non-asynchrone de blocage.
- Je utiliser le modèle suivant pour convertir des rappels async:
SemaphoreSlim ss =newSemaphoreSlim(0);int result =-1;public async TaskMethod(){MethodWhichResultsInCallBack()
await ss.WaitAsync(10000);//Timeout prevents deadlock on failed cblock(ss){//do something with result}}publicvoidCallBack(int _result){lock(ss){
result = _result;
ss.Release();}}
C'est très flexible et peut être utilisé pour des Activités, à l'intérieur de rappel objets ect.
Être prudent, en utilisant de cette façon le mal va créer des blocages ect. Le verrouillage empêche résultat de changer après, si le délai imparti.
Avec Xamarin pour Android de la version 4.7, au moment de cette écriture toujours en version bêta accessible au public, nous pouvons utiliser .NET 4.5 fonctionnalités à mettre en œuvre "asynchrone" méthodes et "attendent" les appels à eux. Il m'a toujours agacé, que si un rappel est nécessaire en Java, la logique de flux de code dans une fonction est interrompue, vous devez continuer le code dans la fonction suivante lorsque la fonction de rappel retourne. Considérons ce scénario:
Je veux rassembler une liste de tous les moteurs de synthèse vocale sur un appareil Android, puis demandez à chacun d'eux les langues qu'il a installé. Le petit “TTS " Setup” de l'activité que j'ai écrit, présente à l'utilisateur de deux cases (“spinners”), une liste de toutes les langues que tous les moteurs TTS sur ce dispositif de soutien. Les autres encadré ci-dessous répertorie toutes les voix disponibles pour la langue sélectionnée dans la première zone, de nouveau disponibles TTS les moteurs.
Idéalement, l'initialisation de cette activité qui devrait se produire dans une fonction, par exemple, dans onCreate(). Pas possible avec la norme de programmation Java parce que:
Cela nécessite deux “perturbateur” rappels – d'abord initialiser le moteur TTS – il devient pleinement opérationnel que lorsque le onInit() est appelée en arrière. Ensuite, lorsque nous avons une initialisé TTS objet, nous avons besoin de l'envoyer à un “android.de la parole.tts.moteur.CHECK_TTS_DATA” l'intention, et de l'attendre en conséquence à nouveau dans notre activité de rappel onActivityResult(). Une autre perturbation de la logique de flux. Si nous sommes itérer sur une liste de TTS les moteurs, même le compteur de la boucle pour cette itération ne peut pas être une variable locale dans une fonction unique, mais doit être effectuée par un membre de classe privée à la place. Assez compliqué à mon avis.
Ci-dessous je vais essayer de décrire le nécessaire de code Java pour atteindre cet objectif.
Désordre code Java pour recueillir tous les moteurs TTS et la voix de leur soutien
Notez que la ligne qui crée une nouvelle TTS objet avec ttsInit rappel, doit être répété 3 fois pour continuer la boucle à travers tous les moteurs disponibles en cas d'exceptions ou d'autres erreurs se produisent. Peut-être que le ci-dessus pourrait être écrit un peu mieux, par exemple, j'ai pensé que je pouvais créer une classe interne pour garder le code de boucle localisée et mon compteur de boucle pour au moins ne pas être un membre de la classe principale, mais il est toujours en désordre. Suggestion pour l'amélioration de ce code Java bienvenue.
Beaucoup plus propre solution: Xamarin C# avec les méthodes asynchrones
Tout d'abord, pour simplifier les choses, j'ai créé une classe de base pour mon Activité qui fournit CreateTtsAsync() pour éviter les PERTURBATIONS 1 dans le code Java ci-dessus, et StartActivityForResultAsync() pour éviter les PERTURBATIONS 2 méthodes.
Maintenant, je peux écrire tout le code en boucle à travers le TTS les moteurs et en leur demandant disponible langues et de voix à l'intérieur d'une fonction, d'éviter une boucle courent à travers les trois fonctions différentes:
Le projet Java et le C# du projet, à l'aide de Visual Studio 2012 avec Xamarin pour Android add-on, sont maintenant disponibles sur GitHub:
https://github.com/gregko/TtsSetup_C_sharp
https://github.com/gregko/TtsSetup_Java
Qu'en pensez-vous?
Apprendre à faire avec Xamarin pour Android essai gratuit est amusant, mais ça vaut le $$ pour Xamarin de licence, puis le poids de chaque APK vous créez pour le Google Play Store d'environ 5 MO en Mono runtimes nous avons à distribuer? Je souhaite que Google a fourni Mono machine virtuelle en tant que norme de la composante du système sur l'égalité des droits avec Java/Dalvik.
P. S. a Examiné le vote sur cet article, et je vois qu'il obtient également quelques voix. Suppose qu'ils doivent être à venir à partir de Java passionnés! 🙂 Encore une fois, des suggestions sur la façon d'améliorer mon code Java sont également les bienvenus.
P. S. 2 - intéressante échange sur ce code d'un autre développeur sur Google+, m'a aidé à mieux comprendre ce qui se passe réellement avec async/await.
Mise à jour 8/29/2013
Dot42 également mis en place, async/await' des mots-clés dans leurs C# de produit pour Android, et j'ai essayé le portage à ce projet de test. Ma première tentative a échoué avec un accident quelque part dans Dot42 bibliothèques, dans l'attente (de manière asynchrone, bien sûr 🙂 ) pour un correctif à partir d'eux, mais il est un fait intéressant, ils ont observé et mis en œuvre lorsqu'il s'agit de "asynchrone" appels à partir de l'Android de l'activité des gestionnaires d'événement:
Par défaut, s'il existe une activité "modification de la configuration" alors que vous êtes en attente d'un résultat d'une longue opération asynchrone à l'intérieur d'une activité de gestionnaire d'événements, comme par exemple, changement d'orientation, l'activité est détruit et recréé par le système. Si après un tel changement, vous le retour d'une "asynchrone" opération au moyen d'un code de gestionnaire d'événements, le " il " de l'objet de l'activité n'est plus valide, et si vous avez stocké un objet en pointant des contrôles au sein de cette activité, ils sont pas valides (ils point à l'ancienne, aujourd'hui détruit des objets).
J'ai frappé ce problème dans mon code de production (en Java) et a travaillé autour de par la configuration de l'activité pour en être informé, et n'est pas détruit et recréé sur de tels événements. Dot42 est venu avec une autre solution, tout à fait intéressant:
L' .configureAwait(ce) extension (plus un plus de ligne de code dans l'activité OnCreate() pour configurer des choses) veille à ce que votre 'ce' objet est encore valide, les points à l'instance actuelle de l'activité, lorsque vous revenez de l'attendent, même si le changement de configuration. Je pense que c'est bon pour au moins être conscients de cette difficulté, lorsque vous commencez à utiliser async/await Android avec le code de l'INTERFACE utilisateur, voir plus writeup sur ce à Dot42 blog: http://blog.dot42.com/2013/08/how-we-implemented-asyncawait.html?showComment=1377758029972#c6022797613553604525
Mise à jour sur Dot42 crash
Async/await crash j'ai vécu est maintenant fixé dans Dot42, et il fonctionne très bien. En fait, mieux que Xamarin du code de la puce de la manipulation de 'cette' objet dans Dot42 entre l'activité de la destruction/de loisirs des cycles. L'ensemble de mon code C# ci-dessus doivent être mis à jour pour tenir compte de ces cycles, et actuellement il n'est pas possible de Xamarin, seulement dans Dot42. Je vais mettre à jour ce code sur la demande d'autres membres, pour l'instant, il semble que cet article n'est pas d'obtenir beaucoup d'attention.
await Task.Run(delegate { Event1.WaitOne(); });
Je pense que c'est vraiment laid. Une bien meilleure solution serait d'utiliserTaskCompletionSource
. Et tous ceux qui videcatch
es aussi ne sont pas une bonne pratique.Génial, merci, @svick! Expérimenter avec TaskCompetionSource et d'améliorer mon code, alors que mon libre Xamarin procès dure. Je suis encore à apprendre le C#, Java et Android, donc tous les commentaires m'aider. Vide captures - dans certains endroits, je n'ai vraiment pas de soins si un buggy moteur TTS, une 3ème partie du code après tout, les accidents, et encore envie de continuer ma boucle. Mais vous avez raison, il sera bon pour au moins la sortie de l'exception à la journal de Débogage pour avoir une idée de ce qui se passe.
souhaiterait un rapide exemple de code montrant comment je pourrais utiliser TaskCompletionSource dans le cadre de mon application, en attente pour les rappels. Notez que nous ne pouvons pas bloquer le thread en cours lors de l'attente pour les rappels de retour. Merci!
Il suffit de regarder la documentation pour les tc. C'est honnêtement une classe très simple; il suffit de regarder l'API est généralement suffisant pour comprendre comment l'utiliser.
Vous utilisez le TCS classe pour créer une tâche que représente lorsque l'événement est prêt, mais sans bloquer un thread. Ensuite, vous pouvez
await
cette tâche. Alors plutôt que de fixer une remise à zéro automatique de l'événement que vous venez de définir le résultat du TCS. C'est ce que svick suggère. C'est le moyen le plus approprié de la signalisation à l'aide de laTask
modèle. À l'aide d'un auto-réinitialisation de l'événement est conçu pour les non-asynchrone de blocage.OriginalL'auteur
- Je utiliser le modèle suivant pour convertir des rappels async:
C'est très flexible et peut être utilisé pour des Activités, à l'intérieur de rappel objets ect.
Être prudent, en utilisant de cette façon le mal va créer des blocages ect. Le verrouillage empêche résultat de changer après, si le délai imparti.
OriginalL'auteur Daniel Roberts