我在Xamarin for Android中编写了一个循环,我使用System.Net.Http.HttpClient
异步加载了5个图像。启动5个请求会立即执行,但在完成所有5个响应之前我没有任何延续 - 大约4秒钟之后。为什么不能单独地,异步地回应?
我不太了解Xamarin中如何处理线程,所以我可能做错了什么。我不应该从UI线程调用它吗?有什么办法可以为HttpClient指定调度程序或线程策略吗?
加载函数的代码:
// This method is called 5 times in a foreach loop, initated from the UI thread, not awaited
private async void LoadAsync(String uri)
{
Bitmap bitmap = await imgSvc.loadAndDecodeBitmap(uri);
image.SetImageBitmap(bitmap);
}
public async Task<Bitmap> loadAndDecodeBitmap(String uri) {
var client = new HttpClient();
byte[] data = await client.GetByteArrayAsync(uri);
Bitmap img = BitmapFactory.DecodeByteArray(data, 0, data.Length);
return img;
}
修改:this repo
中的最小,可重复的示例尝试在该示例中将ImagesToLoad
在5和1之间切换,并查看第一个图像的加载时间如何发生显着变化(在我的计算机上差异大约为2秒)。
答案 0 :(得分:1)
您应该考虑使用Task.WhenAll
同时加载所有内容。您还创建了HttpClient
的多个实例,这可能会对性能产生负面影响。
首先更新通话以避免使用async void
。
ArticleTeaserView.cs
public async Task SetModel(ArticleTeaser model) {
title.SetText(model.promotionContent.title.value, TextView.BufferType.Normal);
description.SetText(model.promotionContent.description.value, TextView.BufferType.Normal);
try {
var uri = Android.Net.Uri.Parse(model.promotionContent.imageAsset.urls[0].url);
Log.Debug(TAG, "Image " + (++ctr) + " load starting...");
await LoadAsync(model.promotionContent.imageAsset.GetUrlWithMinHeight(240), image);
Log.Debug(TAG, "Image " + ctr + " load completed");
} catch (Exception ex) {
Log.Debug(TAG, ex.Message);
}
}
static ImageSvc imgSvc = new ImageSvc(); //should consider injecting service
private async Task<Image> LoadAsync(String uri, ImageView image) {
Bitmap bitmap = await imgSvc.loadAndDecodeBitmap(uri);
image.SetImageBitmap(bitmap);
}
//Use only one shared instance of `HttpClient` for the life of the application
private static HttpClient client = new HttpClient();
public async Task<Bitmap> loadAndDecodeBitmap(String uri) {
byte[] data = await client.GetByteArrayAsync(uri);
Bitmap img = BitmapFactory.DecodeByteArray(data, 0, data.Length);
return img;
}
最后假设你有一个网址集合。您可以创建所有任务并同时调用所有任务。
MainActivity.cs
private event EventHandler LoadData = delegate { };
protected override void OnCreate(Bundle bundle) {
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
InitViews();
LoadData += onDataLoading; // subscribe to event
LoadData(this, EventArgs.Empty); // raise event
}
//async void allowed on event handlers (actual event handler)
//*OnCreate* is not an event handler. Just a based method.
private async void onDataLoading(object sender, EventArgs e) {
LoadData -= onDataLoading;
await LoadDataAsync();
}
private void InitViews() {
//...
}
private async Task LoadDataAsync() {
var svc = new NewsService();
var promContent = svc.syncLoadStrong();
await BindView(promContent);
}
private async Task BindView(ArticleList list) {
Log.Debug(TAG, "Binding MainActivity");
ViewGroup scroller = FindViewById<ViewGroup>(Resource.Id.scroller);
if (list != null) {
var tasks = new List<Task>();
foreach (ArticleTeaser teaser in list) {
var atv = new ArticleTeaserView(this, null);
tasks.Add(atv.SetModel(teaser));
scroller.AddView(atv);
}
await Task.WhenAll(tasks);
}
}
确保您没有将.Result
,.Wait()
等阻止调用与async / await调用混合使用,并且除非是针对事件处理程序,否则请避免使用async void
。
答案 1 :(得分:0)
我的建议是使用HttpClient对象的单个实例。如上所述resizeColumnsToContents,您可以重复使用它来处理多重实例。
您遇到的问题是因为每次创建HttpClient对象时,都会打开一个连接并且此操作需要一些时间。同样,处理每个实例都需要关闭连接,这可能会对其他请求产生影响。
答案 2 :(得分:0)
HttpClient
在并行运行时严重缩放。它似乎只在场景后面使用了几个(1?)线程,这些线程是相互依赖的,并且可以相互阻塞,无论我们将它包装在Task.Run
中。在Client.GetByteArrayAsync()
与Task.Delay(1000)
交换后,我得出了这个结论。他们的行为截然不同。
并行运行1或10 Task.Delay(1000)
两者都能在刚好超过1200毫秒时给出第一个结果。请参阅the repo中的提交ff2a8e9
- 与LoadAndDecodeBitmapFake
交换LoadAndDecodeBitmapFake
。
运行1 Client.GetByteArrayAsync()
给我第一个结果〜1300ms后运行其中10个给我第一个结果~5000ms后。提交561691f
事实证明,最佳解决方案是以串行方式运行它们 - 等待每次执行而不是将它们混为一谈并在所有10上运行await Task.WhenAll()
。请参阅提交eaa0f18
。然后我得到~1400ms后的第一个结果和~2000ms后的最后一个结果!
听起来像HttpClient
的设计缺陷,或者在重写/包裹到monodroid中时出现了一些损坏。