我正在为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
。发生以下两件事之一:
System.AggregateException
,并显示以下消息: 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);
答案 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 afresh:retry 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 Lost
,myTask
(等等;可能更多)。
使用此(临时,诊断)修正还应证明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策略如何(或者如果您使用手工构建的for
或Task
循环,则相同重试)。
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"
实例。