如何在C#中使用async/await和Xamarin或Dot42实现Android回调?
如何在C#中使用async/await和Xamarin for Android实现回调?这与Android的标准Java编程相比如何?由于Xamarin的Android版本为4.7,在撰写本文时,它仍处于公开的beta版本中,我们可以使用.NET 4.5的功能来实现“异步”方法和对它们的“等待”调用。如果Java中需要任何回调,那么函数中的逻辑代码流就会中断,当回调返回时,您必须在下一个函数中继续代码,这一直困扰着我。考虑这种情况: 我想收集一份Android设备上所有可用TextToSpeech引擎的列表,然后询问每个引擎安装了哪些语言。我编写的小“TTS设置”活动向用户显示了两个选择框(“微调器”),其中一个列出了此设备上所有TTS引擎支持的所有语言。下面的另一个框列出了第一个框中所选语言的所有可用语音,同样来自所有可用的TTS引擎 理想情况下,此活动的所有初始化都应该在一个函数中进行,例如在onCreate()中。标准Java编程不可能,因为: 这需要两个“中断性”回调—首先初始化TTS引擎—只有在回调onInit()时,它才能完全运行。然后,当我们有一个初始化的TTS对象时,我们需要向它发送一个“android.speech.TTS.engine.CHECK_TTS_DATA”意图,并在ActivityResult()上的活动回调中再次等待它的结果。逻辑流程的另一个中断。如果我们在一个可用TTS引擎列表中进行迭代,那么即使此迭代的循环计数器也不能是单个函数中的局部变量,而是必须成为私有类成员。在我看来相当混乱 下面我将尝试概述实现这一点所需的Java代码 收集所有TTS引擎及其支持的语音的凌乱Java代码 .configureAwait(this)扩展名(加上activity OnCreate()中用于设置内容的另一个代码行)确保您的“this”对象仍然有效,在您从wait返回时指向活动的当前实例,即使发生配置更改。我认为至少应该意识到这一困难,当您开始在Android UI代码中使用async/await时,请参阅Dot42博客上的更多内容: 更新Dot42崩溃如何在C#中使用async/await和Xamarin或Dot42实现Android回调?,c#,android,async-await,xamarin,dot42,C#,Android,Async Await,Xamarin,Dot42,如何在C#中使用async/await和Xamarin for Android实现回调?这与Android的标准Java编程相比如何?由于Xamarin的Android版本为4.7,在撰写本文时,它仍处于公开的beta版本中,我们可以使用.NET 4.5的功能来实现“异步”方法和对它们的“等待”调用。如果Java中需要任何回调,那么函数中的逻辑代码流就会中断,当回调返回时,您必须在下一个函数中继续代码,这一直困扰着我。考虑这种情况: 我想收集一份Android设备上所有可用TextToSpeec
我所经历的异步/等待崩溃现在在Dot42中得到了修复,效果非常好。实际上,比Xamarin代码更好,因为Dot42在活动销毁/重新创建周期之间智能处理了“this”对象。我上面所有的C#代码都应该更新,以考虑到这样的循环,目前在Xamarin中不可能,只有在Dot42中。我将根据其他SO成员的要求更新该代码,目前这篇文章似乎没有得到太多关注。我使用以下模型将回调转换为异步:
SemaphoreSlim ss = new SemaphoreSlim(0);
int result = -1;
public async Task Method() {
MethodWhichResultsInCallBack()
await ss.WaitAsync(10000); // Timeout prevents deadlock on failed cb
lock(ss) {
// do something with result
}
}
public void CallBack(int _result) {
lock(ss) {
result = _result;
ss.Release();
}
}
这非常灵活,可用于活动、回调对象内部等
小心,使用错误的方法会造成死锁。如果超时超时,锁将防止在超时后更改结果
wait Task.Run(委托{Event1.WaitOne();})我觉得这真的很难看。更好的解决方案是使用TaskCompletionSource
。而且所有那些空的catch
es也不是一个好的做法。太好了,谢谢你,@svick!将试用TaskCompetionSource并改进我的代码,而我的免费Xamarin试用将持续。我还在学习C#、Java和Android,所以所有的评论都对我有帮助。空捕获-在某些地方,我真的不在乎有缺陷的TTS引擎(毕竟是第三方代码)是否崩溃,并且仍然想继续我的循环。但你是对的,至少将异常输出到调试日志中会更好,以便了解发生了什么。@svick-希望能提供一个快速的代码示例,演示如何在应用程序的上下文中使用TaskCompletionSource,等待回调。请注意,在等待回调返回时,我们无法阻止当前线程。谢谢@格雷克。只需查看TCS的文档。老实说,这是一门非常简单的课;只要看一下它的API就足以知道如何使用它。@GregK。您可以使用TCS类创建一个任务,该任务表示事件准备就绪的时间,但不会阻塞线程。然后你可以等待那项任务。然后,您只需设置TCS的结果,而不是设置自动重置事件。这就是斯维克的建议。这是使用任务
模型发出信号的适当方式。使用自动重置事件是为非异步阻塞而设计的。
// 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.
public class TtsAsyncActivity : Activity, TextToSpeech.IOnInitListener
{
protected const String TAG = "TtsSetup";
private int _requestWanted = 0;
private TaskCompletionSource<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 = new TaskCompletionSource<Java.Lang.Object>();
var tts = new TextToSpeech(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 = new TaskCompletionSource<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;
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if (requestCode == _requestWanted)
{
_tcs.SetResult(data);
}
}
void TextToSpeech.IOnInitListener.OnInit(OperationResult status)
{
Log.Debug(TAG, "OnInit() status = " + status);
_tcs.SetResult(new Java.Lang.Integer((int)status));
}
}
// Method of public class TestVoiceAsync : TtsAsyncActivity
private async void GetEnginesAndLangsAsync()
{
_tts = new TextToSpeech(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 = new EngLang(ei);
_allEngines.Add(el);
Log.Debug(TAG, "Engine: " + ei.Name + " initialized correctly.");
var intent = new Intent(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 continue
IList<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.
...
}
var data = await webClient
.DownloadDataTaskAsync(myImageUrl)
.ConfigureAwait(this);
SemaphoreSlim ss = new SemaphoreSlim(0);
int result = -1;
public async Task Method() {
MethodWhichResultsInCallBack()
await ss.WaitAsync(10000); // Timeout prevents deadlock on failed cb
lock(ss) {
// do something with result
}
}
public void CallBack(int _result) {
lock(ss) {
result = _result;
ss.Release();
}
}