2017-09-25 16 views
0

iOSとAndroid用のXamarin.Formsアプリケーションを作成しています。Azureサーバーでデータとローカルsqliteデータベースとオンラインを保持しています。私のアプリは常にConnectivityプラグインでチェックしているインターネット接続が必要ですが、ユーザーが途中でセル受信を失った場合に例外がスローされることがあります。C#Polly async-await:再試行前にユーザーの確認を待つ

エラーが発生した場合、リクエストを再試行するためのすべてのサーバー要求を呼び出すことができるメソッドが必要です。私はまた、再試行する前にユーザーに入力を求める機能が欲しいと思います。流れは次のようになります。サーバーへ

コール - >例外がキャッチ - >彼らは再試行したい場合はユーザーに依頼 - >

を再試行私はのtry/catchを処理するように設定されているPollyパッケージを見つけましたがC#で再試行します。

public class WebExceptionCatcher<T, R> where T : Task<R> 
{  
    public async Task<R> runTask(Func<T> myTask) 
    { 
     Policy p = Policy.Handle<WebException>() 
     .Or<MobileServiceInvalidOperationException>() 
     .Or<HttpRequestException>() 
     .RetryForeverAsync(onRetryAsync: async (e,i) => await RefreshAuthorization()); 

     return await p.ExecuteAsync<R>(myTask); 
    } 
} 

マイRefreshAuthorization()方法は、単純にメインスレッド上で現在のページにDisplayAlertが表示されます:私はこれをデバッグし、私のインターネット接続を切断したとき

private async Task RefreshAuthorization() 
{ 
    bool loop = true; 
    Device.BeginInvokeOnMainThread(async() => 
    { 
     await DisplayAlert("Connection Lost", "Please re-connect to the internet and try again", "Retry"); 
     loop = false; 
    }); 

    while (loop) 
    { 
     await Task.Delay(100); 
    } 
} 

私は現在、このように私のコードを設定しています。 DisplayAlertは表示されません。 2つのいずれかが起こる:

System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. ---> System.Net.Http.HttpRequestException: An error occurred while sending the request

、誰もが知っています:

  1. を実行
  2. System.AggregateException次のメッセージがスローさを完了せずに何度も私のタスクを呼び出すために続けてタスクが失敗したときに実行を一時停止し、ユーザーが再開するのを待つ方法

    UPDATE:

    Device.BeginInvokeOnMainThread方法の内DisplayAlertに電話を入れた後、私は今AggregateExceptionを回避する方法を発見しました。しかし、今私は別の問題があります。

    インターネットから接続を解除すると、DisplayAlertが想定どおりにポップアップします。プログラムがonRetry関数を終了する前に、再試行をクリックするのを待ちますので、RetryForeverAsync待機が正しく機能しています。問題は、インターネットに再接続して再試行すると、もう一度やり直しが失敗するということです。だから私はインターネットに接続しているにもかかわらず、私は再接続を求められて無限ループに陥っています。 RetryForeverAsyncは古い例外を単に再投げているようです。

    Task<TodoItem> t = App.MobileService.GetTable<TodoItem>().LookupAsync(id); 
    WebExceptionCatcher<Task<TodoItem>, TodoItem> catcher = new WebExceptionCatcher<Task<TodoItem>, TodoItem>(); 
    

    が、私はその後、接続が再確立されたときに再試行に失敗すると同じ結果とrunTask、両方を呼び出すの2種類の方法を試してみた:ここ

    は私がrunTask()を呼んでいる方法です

    TodoItem item = await catcher.runTask(() => t); 
    

    か:

+0

ありがとう、あなたは 'RetryForeverAsync'を試しましたか? – JSteward

+0

ええ、それはAggregateExceptionを取得したときに実際に私が呼び出していたものでした。 – cvanbeek

答えて

0

は、COMとして.RetryForeverAsync(...)使用する必要がありますメンターが指摘した。次に、再試行時のデリゲートが非同期であるため、onRetryAsync:も使用する必要があります。したがって:

.RetryForeverAsync(onRetryAsync: async (e,i) => await RefreshAuthorization());


あなたが見たエラーを説明するために:問題のコードサンプルでは、​​onRetry:を使用することによって、あなたは、同期onRetryデリゲート(帰国無効)を使用するように指定されています、それに非同期デリゲートを割り当てます。

これにより、async-delegate-assigned-to-sync-paramがasync voidになります。呼び出しコードは/ できません待ってください。 async void代理人が待機していないので、実行された代理人は実際に継続的に再試行されます。

System.AggregateException: A Task's exception(s) were not observedが原因である可能性があります。または、myTaskの署名が一致しないことが原因である可能性があります(この回答を投稿した時点ではqには表示されません)。 質問するUPDATE、さらにコメントへの応答で

EDIT

日時:

RetryForeverAsyncは、単に古い例外を再スローしているようです。

私はポリーは確かにループラウンド毎時間経過しFunc<Task<R>>を呼び出し、唯一Func<Task<R>>の新鮮な実行がスローすることを何でも例外を再スローすること(ポリー著者/メンテナとして)知っています。 retry loopのたびにasync retry implementationretries the user delegate afreshを参照してください。

RefreshAuthorization()が呼び出しポリシーコードの実行を継続的にブロックしているかどうかを確認するために、呼び出しコードに対して次のような(一時的、診断的)修正を試みることができます。

public class WebExceptionCatcher<T, R> where T : Task<R> 
{  
    public async Task<R> runTask(Func<T> t) 
    { 
     int j = 0; 
     Policy p = Policy.Handle<WebException>() 
     .Or<MobileServiceInvalidOperationException>() 
     .Or<HttpRequestException>() 
     .RetryForeverAsync(onRetryAsync: async (e,i) => await RefreshAuthorization()); 

     return await p.ExecuteAsync<R>(async() => 
     { 
      j++; 
      if ((j % 5) == 0) Device.BeginInvokeOnMainThread(async() => 
      { 
       await DisplayAlert("Making retry "+ i, "whatever", "Ok"); 
      }); 
      await myTask; 
     }); 
    } 
} 

RefreshAuthorization())が正しくブロックしている場合は、Making retry 5ダイアログが表示される前にConnection Lostポップアップを5回を却下する必要があります。

RefreshAuthorization())が呼び出しコードをブロックしていない場合は、再接続して最初にダイアログを閉じる前に、ポリシーがバックグラウンドで複数回(失敗した)試行を続けていました。このシナリオが成立した場合、Connection Lostポップアップを一回閉じると、次のConnection Lostポップアップの前にポップアップMaking retry 5Making retry 10などが表示されます。

この(一時的、診断的)修正は、毎回、渡されたデリゲートをPollyが実行していることを示す必要があります。同じ例外がmyTaskによってスローされている場合、それはmyTaskの問題である可能性があります - それについてもっと知り、そこで深く掘り下げる必要があるかもしれません。


UPDATE発信者の2回目の更新が開始に応答したので

「ここで私はrunTask():を呼んでいる方法です」:あなたは、再試行が失敗していると仮定されていますが、あなたはしないコードを構築しました実際には再試行を行います。これはしか関わらず、ポリーポリシーの(または同じように、あなたが手を使用した場合、これらのコード行のトラバーサルごとに一度App.MobileService.GetTable<TodoItem>().LookupAsync(id)を呼び出し

Task<TodoItem> t = App.MobileService.GetTable<TodoItem>().LookupAsync(id); 
TodoItem item = await catcher.runTask(() => t); // Or same effect: TodoItem item = await catcher.runTask(async() => await t); 

残りの問題の原因は、次の2行です-built whileまたはfor再試行のループ)。

Taskインスタンスは「再実行可能」ではありません。Taskのインスタンスは、1回の実行のみを表すことができます。この行では:

Task<TodoItem> t = App.MobileService.GetTable<TodoItem>().LookupAsync(id); 

あなたは一度だけLookupAsync(id)を呼び出し、tに(それが完了したか、障害時)LookupAsyncは、その1つの実行結果を実行していることを表してTaskインスタンスを割り当てます。 2行目では、Taskの同じインスタンスを常に返すラムダ() => tを作成します。これは1回の実行を表しています。 (tの値は決して変更されず、funcがそれを返すたびに、最初の実行の結果はLookupAsync(id)となります)。したがって、インターネット接続がないために最初の呼び出しが失敗した場合、Pollyのリトライポリシーを実行すると、最初の実行の失敗を表すawait -ing Taskが保持され、元の失敗は本当に再スローされ続けます。問題を説明するために、画像のうちのTaskを取るために

、それは少し、このコードを書くようなものだ:

int i = 0; 
int j = i++; 
Func<int> myFunc =() => j; 
for (k=0; k<5; k++) Console.Write(myFunc()); 

と、それは、それが印刷されるか(j 5の値を12345を印刷するのではなくことを期待します回)​​。単に

それを動作させるために、:

TodoItem item = await catcher.runTask(() => App.MobileService.GetTable<TodoItem>().LookupAsync(id)); 

次にラムダの各呼び出しは、その新鮮なコールを表すTask<ToDoItem>の新しいインスタンスを返す、新たに.LookupAsync(id)を呼び出します。

+0

これは 'System.AggregateException'をスローします。私はクラス全体を含めるように質問を更新します – cvanbeek

+0

Aは別の時間に実行し、別の例外を持っています: 'System.Reflection.TargetInvocationException:例外が呼び出しのターゲットによってスローされました。 ---> Java.Lang.RuntimeException:Looper.prepare()を呼び出していないスレッド内でハンドラを作成できません。 – cvanbeek

+0

@cvanbeekその特定のAndroidの例外に精通していません。しかし、非同期Pollyの使い方が必要な待機を提供する場合(他の例外が修正された場合) –

関連する問題