C#Polly async-await:等待用户确认,然后重试

时间:2017-09-25 22:14:31

标签: c# asynchronous xamarin async-await polly

我正在为iOS和Android创建一个Xamarin.Forms应用程序,我将数据和本地sqlite数据库保存在Azure服务器中。虽然我的应用程序需要互联网连接,并且始终使用连接插件进行检查,但我发现如果用户在请求过程中丢失了小区接收,我有时会抛出异常。

我希望有一个方法可以调用我的所有服务器请求,如果发生错误,将重试请求。我还希望能够在重试之前询问用户输入。流程看起来像这样:

致电服务器 - >被捕的例外 - >询问用户是否要重试 - >重试

我找到了Polly包,它被设置为处理C#中的try / catch重试。我目前的代码设置如下:

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。发生以下两件事之一:

  1. 执行继续一遍又一遍地调用我的任务而不完成
  2. 抛出System.AggregateException,并显示以下消息:
  3. 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

    有人知道如何在任务失败时成功暂停执行,并等待用户恢复吗?

    更新:

    DisplayAlert方法中调用Device.BeginInvokeOnMainThread之后,我现在找到了解决AggregateException的方法。但是,现在我还有另一个问题。

    一旦我断开互联网连接,DisplayAlert会弹出它应该的样子。程序等待我在完成onRetry功能之前单击重试,以便RetryForeverAsync等待正常工作。问题是,如果我重新连接到互联网然后重试,它会再次失败,并再次失败。因此,即使我已连接到互联网,我也陷入了被要求重新连接的无限循环中。似乎RetryForeverAsync只是重新抛出旧的异常。

    以下是我致电runTask()的方式:

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

    然后我尝试了两种不同的方法来调用runTask,这两种方法都会导致在重新建立连接时失败重试的结果相同:

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

    或:

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

1 个答案:

答案 0 :(得分:1)

您需要使用.RetryForeverAsync(...)作为评论员注明。然后,由于您的重试委托是异步的,您还需要使用onRetryAsync:。因此:

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

解释您看到的错误:在问题的代码示例中,使用onRetry:,您指定要使用同步onRetry委托(返回void),但随后分配一个异步委托给它。

这会导致async-delegate-assigned-to-sync-param变为async void;调用代码不会&#em> 等待等待。由于async void代表没有等待,您执行的代表确实会被连续重试。

System.AggregateException: A Task's exception(s) were not observed可能是由myTask引起的,或者可能是由Func<Task<R>>签名中的某些不匹配引起的(在发布此答案时q中不可用)。

编辑以回应更新提问及进一步评论:

回复:

  

似乎RetryForeverAsync只是重新抛出旧的异常。

我知道(作为Polly作者/维护者)Polly肯定每次循环调用传递的Func<Task<R>>,并且只会重新抛出RefreshAuthorization()的新执行抛出的任何异常。每次async retry implementation都会看到retries the user delegate afreshretry loop

您可以尝试对您的调用代码进行以下(临时,诊断)修改,以查看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())

如果Connection Lost被正确阻止,则需要在显示Making retry 5对话框之前五次忽略RefreshAuthorization())弹出窗口。

如果Connection Lost未阻止调用代码,则在重新连接并首先解除Connection Lost对话框之前,该策略将继续在后台进行多次(失败)尝试。如果这种情况成立,然后只关闭Making retry 5弹出窗口,那么在下一个Making retry 10弹出窗口之前,您会看到弹出窗口Connection LostmyTask(等等;可能更多)。

使用此(临时,诊断)修正还应证明Polly每次都重新执行您传递的代理。如果myTask引发相同的异常,这可能是runTask():的问题 - 我们可能需要了解更多相关信息,并深入挖掘。

更新以响应发起人的第二次更新开始&#34;以下是我如何致电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); &#34;

所以:你一直在假设重试失败了,但你构建的代码实际上并没有重试。

剩下的问题的根源是这两行:

App.MobileService.GetTable<TodoItem>().LookupAsync(id)

每次遍历这些代码行时,这只会调用while一次,无论Polly策略如何(或者如果您使用手工构建的forTask循环,则相同重试)。

Task实例无法重新运行&#39;:Task<TodoItem> t = App.MobileService.GetTable<TodoItem>().LookupAsync(id); 的实例只能代表一次执行。在这一行:

LookupAsync(id)

您只需调用t一次,并将Task实例分配给() => t实例,该实例表示正在运行的LookupAsync以及(当它完成或出现故障时)该执行的结果。然后在第二行中构造一个lambda Task,它总是返回t的同一个实例,表示一次执行。 (LookupAsync(id)的值永远不会改变,每次func返回它时,它仍然代表await的首次执行的结果。)。因此,如果第一次调用因为没有互联网连接而失败,那么您所做的所有Polly重试策略都是Task - 表示Task代表首次执行的操作&#39; s失败,所以最初的失败确实不断被重新抛出。

要从图片中取出int i = 0; int j = i++; Func<int> myFunc = () => j; for (k=0; k<5; k++) Console.Write(myFunc()); 来说明问题,这有点像编写此代码:

12345

并期望它打印j而不是(打印的内容,11111的值五次)TodoItem item = await catcher.runTask(() => App.MobileService.GetTable<TodoItem>().LookupAsync(id));

为了使其有效,只需:

.LookupAsync(id)

然后每次调用lambda都会重新调用Task<ToDoItem>,返回一个代表该新调用的新'navigator' in window different with typeof window.navigator !== "undefined" 实例。