2017-09-10 3 views
0

Xamarin for Androidで、System.Net.Http.HttpClientを使用して5つの画像を非同期にロードするループを作成しました。 5つのリクエストの開始は即座に実行されますが、のいずれかがになることはありません。レスポンスが個別に非同期に来ないのはなぜですか?Xamarinで非同期HttpClient呼び出しのループがすべて終了するまでブロックされるのはなぜですか?

私は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

で最小、再現可能な例は、参照5と1との間にその例でImagesToLoadを切り替えてみてください方法のロード時間最初に画像が劇的に変化します(コンピュータで約2秒の違いがあります)。

Load log 10 images
Load log if I only load 1

+1

非同期呼び出しのループを問題の再現に使用できる[mcve]として表示できますか。あなたがリンクしている解決策は裸の骨プロジェクトだけです。 – Nkosi

+0

これらのメソッドを呼び出すコードが含まれるように質問を編集してください。 – Progman

+0

申し訳ありませんが間違ったブランチにリンクしました。支店「ラップトップ」https://github.com/Nilzor/newsapp/tree/laptop/xamarin-news/xam-android-news/xam-android-newsをチェックしてください。私は明日、完全で検証可能な最小限の例を提供します – Nilzor

答えて

0

HttpClientパラレルで実行するとひどくひどくなります。これはシーンの背後にある数少ない(1?)スレッドを使用しているように見えますが、これは相互に依存し、Task.Runにラップしても互いにブロックすることができます。 Client.GetByteArrayAsync()Task.Delay(1000)と交換した後、私はこの結論に達しました。彼らは大きく異なっている。

パラレルで1または10を実行すると、〜1200msのちょうど上の最初の結果が得られます。コミットff2a8e9the repoに、スワップLoadAndDecodeBitmapFakeLoadAndDecodeBitmapFakeに変更してください。

ランニング1 Client.GetByteArrayAsync()私には〜1300ms後の最初の結果が表示されますが、10回実行すると〜5000ms後の最初の結果が得られます。 Seコミット561691f

最適な解決策は、それらを一括実行するのではなく毎回実行することです。await Task.WhenAll()をすべて実行してください。eaa0f18を参照してください。それから、私は〜1400ms後に最初の結果を得て、〜2000ms後に最後の結果を得ます!

HttpClientなどの設計上の瑕疵、またはモノドローイングに書き直し/折り返しで壊れたものがあります。

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; 
} 

は、最後にあなたがURLのコレクションを持っていると仮定します。すべてのタスクを作成し、それらのタスクをすべて同時に呼び出します。

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 voidを使用しないでください。

+0

残念ながら、ここでは良いヒントはありませんでした。私はこのブランチで実装しました:https://github.com/Nilzor/newsapp/tree/laptop/xamarin-news/xam-android-news/xam-android-news。ルートメソッドがAndroidのonCreateであるため完全にriddingすることに苦しんでいます - – Nilzor

+0

@Nilzorのシグネチャを変更することはできません。独自のイベントとイベントハンドラを作成し、onCreate – Nkosi

0

私は、HttpClientオブジェクトのインスタンスを1つ使用することをお勧めします。 hereのように、マルチプルインスタンスを処理するために再利用することができます。

問題は、HttpClientオブジェクトが作成されるたびに接続が開かれ、この操作に時間がかかるためです。同様に、各インスタンスを廃棄するには、接続を閉じる必要があり、これが他の要求に影響を与える可能性があります。

関連する問題