Xamarin Android中不等待对异步方法的调用
本文关键字:异步方法 调用 等待 Android Xamarin | 更新日期: 2023-09-27 18:30:01
我有一个片段,用于返回从REST API接收的文章列表。REST API是async
,我使用await
来等待它完成。完成后,我将数据(即List<Article>
)传递给一个名为listofarticles
的变量,并使用它创建一个ArrayAdapter
。
据我所知,我在这个片段的OnCreate
方法中执行了上述代码,该方法应该在OnCreateView
之前运行。
我的代码可以在活动中工作,但不能在片段中工作。
问题:未等待对不可用REST方法的调用。
当逐行调试时,它是这样的:
-
调用CCD_ 8。处理到行,在那里我调用awaitable方法(应该填充
listofarticles
),然后简单地跳到OnCreateView
。因此listofarticles
为空。 -
调用并处理CCD_ 12。在这一点上,程序只是返回到它所调用的活动。
-
我无法再调试它,因为Xamarin Studio认为我已经完成了,但在模拟器中,它刚刚开始调用REST方法,因此
listofarticles
中的数据是无用的,因为我现在无法将其传递给Adapter。
为什么会发生这种情况,如何解决??
public class LatestNewsFragment : Android.Support.V4.App.Fragment, ListView.IOnItemClickListener
{
private Context globalContext = null;
private List<Article> listofarticles;
private ArrayAdapter<Article> listAdapter;
public LatestNewsFragment()
{
this.RetainInstance = true;
}
public async override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
globalContext = this.Context;
//Create a progress dialog for loading
ProgressDialog pr = new ProgressDialog(globalContext);
pr.SetMessage("Loading data");
pr.SetCancelable(false);
var rest = new RestAccess();
pr.Show();
//Download the data from the REST API
listofarticles = await rest.ListArticlesAsync ("SomeSource");
pr.Hide();
Console.WriteLine (listofarticles.Capacity);
listAdapter = new ArrayAdapter<Article>(globalContext, Android.Resource.Layout.SimpleListItem1, listofarticles);
}
public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = inflater.Inflate(Resource.Layout.Fragment_LatestNews, null);
ListView listView = view.FindViewById<ListView>(Resource.Id.list_latestnews);
listView.Adapter = listAdapter;
listView.OnItemClickListener = this;
return view;
}
}
如本答案所述https://stackoverflow.com/a/32656807/1230302在OnCreate()中分段执行任何与视图相关的操作都不是一个好主意。
此外,如果您将OnCreate()设为异步方法,则其他片段生命周期事件(请参阅http://developer.android.com/guide/components/fragments.html)将在等待线后立即呼叫。现在的问题是,你不能依赖等待后代码的运行顺序,如果你的REST API很慢,OnCreateView()可能已经完成了,你会很好,但如果API很快,OnCreateView()可能仍在运行,你的应用程序将崩溃。
为什么不使用OnActivityCreated()加载数据?你可以确定所有的东西都在这个文件中初始化了,比如:
private ListView listView;
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = inflater.Inflate(Resource.Layout.Fragment_LatestNews, null);
listView = view.FindViewById<ListView>(Resource.Id.list_latestnews);
listView.OnItemClickListener = this;
return view;
}
public override async void OnActivityCreated(Bundle savedInstanceState)
{
base.OnActivityCreated(savedInstanceState);
//Download the data from the REST API
var rest = new RestAccess();
var listofarticles = await rest.ListArticlesAsync("SomeSource");
listView.Adapter = new ArrayAdapter<Article>(Context, Android.Resource.Layout.SimpleListItem1, listofarticles);
}
async并不意味着"等待,不运行任何其他代码"。它的意思是"允许其他代码在这个上下文中等待这个调用时运行。"每个async块都是自己的上下文,没有等待将运行自己的程序。因此,对其他方法的调用在等待该调用完成甚至启动时运行是很正常的。您不应该以这种方式依赖订单,而应该正确地同步代码。
最好是在创建完所有这些视图之后获取数据,然后将其分配给适配器。这样,一切都是可以预测的,如果必要的话,你甚至可以显示一个合适的装载指示器。
这个问题的解决方案是创建一个类型为View
的字段变量来存储片段将返回的膨胀视图,并使用该视图在OnCreate
方法中获取布局文件。在await
之后,我们可以用数据填充ListView
。所以最后的代码是:
public class LatestNewsFragment : Android.Support.V4.App.Fragment, ListView.IOnItemClickListener
{
private Context globalContext = null;
private List<Article> listofarticles;
View view;
public LatestNewsFragment()
{
this.RetainInstance = true;
}
public async override void OnCreate (Bundle savedInstanceState)
{
base.OnCreate (savedInstanceState);
globalContext = this.Context;
//Create a progress dialog for loading
Android.App.ProgressDialog pr = new Android.App.ProgressDialog(globalContext);
pr.SetMessage("Loading data");
pr.SetCancelable(true);
var rest = new RestAccess();
pr.Show();
listofarticles = await rest.ListArticlesAsync ("SomeSource");
pr.Hide();
ArrayAdapter<Article> listAdapter = new ArrayAdapter<Article>(globalContext, Android.Resource.Layout.SimpleListItem1, listofarticles);
ListView listView = view.FindViewById<ListView>(Resource.Id.list_latestnews);
listView.Adapter = listAdapter;
listView.OnItemClickListener = this;
}
public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
view = inflater.Inflate(Resource.Layout.Fragment_LatestNews, null);
return view;
}
}