出错时再次循环
Posted
技术标签:
【中文标题】出错时再次循环【英文标题】:Do loop again when error 【发布时间】:2016-07-28 15:45:47 【问题描述】:我试图阅读所有内容,但我有点卡在一个问题上。 通过使用 bigrquery,我创建了对 Google BigQuery 的查询以获取数据 - 不幸的是,有时我的查询由于超时而无法工作。 Q 是一个 SQL-Query,BQ 应该存储从 BigQuery 下载的数据。
有人知道每次 tryCatch 给我一个错误时如何重新循环吗?
到目前为止我得到了这个:
BQ_Foo <- NULL
tryCatch(
repeat
BQ_Foo <- query_exec(Q_foo,"bigquery")
if(is.list(BQ_Foo) == TRUE)break
,error=function(e)cat("ERROR : Query not loaded!", "\n")
)
编辑:
我再次尝试了我的第一种方法,这次我收到了这个错误消息:
curl::curl_fetch_memory(url, handle = handle) 中的错误: 操作被应用程序回调中止
有人知道怎么处理吗?
【问题讨论】:
忘了解释:如果查询没有加载 is.list(BQ_Foo) 为 FALSE 也许withRestart
能帮上忙 (ftom ?tryCatch
)
我还在吸收。另一篇文章在Hadley's Advanced R。
cesco,这个答案对你有用吗?如果是这样,请“接受”它(答案旁边的复选标记),否则让我们知道什么不起作用。谢谢!
大家好,很抱歉让您久等了——我现在正在尝试将它实现到我的代码中。会尽快更新。
【参考方案1】:
广泛基于 r2evans 的回答,以下是如何使用 withRestarts
做同样的事情,并得到 This blog post 的一些帮助:
set.seed(2)
foo <- NULL
operation <- function(x,tries)
message(paste("x is",x,"remaining tries",tries))
withRestarts(
tryCatch(
if (runif(1) < x) stop("fail!") else 1
,
error=function(e) invokeRestart("retry")),
retry = function()
message("Retrying")
stopifnot(tries > 0)
operation(x,tries-1)
)
> operation(0.9,5)
# x is 0.9 remaining tries 5
# Retrying
# x is 0.9 remaining tries 4
# Retrying
# x is 0.9 remaining tries 3
# Retrying
# x is 0.9 remaining tries 2
# Retrying
# x is 0.9 remaining tries 1
[1] 1
这是一种递归调用,所以你可以在再次调用函数之前做任何你想做的事情。
您可以在 tryCatch 错误处理程序中以相同的方式执行此操作,使用重新启动处理程序的兴趣在于调用特定函数,如果您有两个 tryCatch 您想要几乎相同的处理程序行为,那么您可以添加一个参数并使用每个 try catch 的处理程序相同,即:
testfun <- function(x)
withRestarts(
tryCatch(
ifelse(runif(1) < 0.5,stop("Error Message"),warning("Warning message"))
,
warning=function(e) invokeRestart("logger", level="warning", message=e ) ,
error=function(e) invokeRestart("logger", level="error", message=e )
)
,
logger = function(level,message)
message(date()," [",level,"]: ",message[['message']])
)
给予:
> set.seed(2)
> testfun()
Fri Jul 29 14:15:11 2016 [error]: Error Message
> testfun()
Fri Jul 29 14:15:12 2016 [warning]: Warning message
> testfun()
Fri Jul 29 14:15:13 2016 [warning]: Warning message
> testfun()
Fri Jul 29 14:15:13 2016 [error]: Error Message
这里的主要兴趣是记录器方法的分解和减少代码重复。
【讨论】:
嗯,它有点工作,但它仍然收到超时警告:Fri Jul 29 15:15:53 2016 [error]: Timeout was reached
@cesco 什么样的工作?这个输出看起来像我的第二个例子,它绝不是递归的(记录器函数无论如何都不会调用父函数)并且只是一个通用处理程序的简化示例......花一些时间来真正阅读答案以获得每个示例的作用。
@Tensibai - 你是对的,对不起!我对它有点陌生-但是我通过您的示例自己工作,并且效果很好!非常感谢!
@Tensibai,正如你所说,这会产生一个递归调用树。当我尝试它时,第一次失败时,我看到一堆 6 个函数;第二次,我看到 12,第三次 18 等等。有没有办法在不深入的情况下实现这个? (我不害怕递归,但通常更喜欢在尾递归可以优化过程的某些行为时使用它,我认为这不一定在 R 中完成。)
FWIW 影响几乎为零,但如果您想避免递归,请使用循环。我确实更喜欢使用 tryCatch 处理程序,因为我发现它更易于维护,但这只是个人意见。【参考方案2】:
简单的解决方案
您可能会从将 repeat/while 放在 tryCatch
之外的一些天真的尝试开始,如下所示:
set.seed(2)
foo <- NULL
while (is.null(foo))
foo <- tryCatch(
if (runif(1) < 0.9) stop("fail!") else 1
,
error = function(e) message("err"); NULL;
)
# err
# err
# err
# err
message("success: ", foo)
# success: 1
不幸的是,您引入了循环永远不会返回的可能性。为了防止这种情况,您可以尝试计数器...
不那么天真的解决方案
set.seed(2)
foo <- NULL
max_attempts <- 3
counter <- 0
while (is.null(foo) && counter < max_attempts)
counter <- counter + 1
foo <- tryCatch(
if (runif(1) < 0.9) stop("fail!") else 1
,
error = function(e) message("err"); NULL;
)
# err
# err
# err
if (is.null(foo)) message("final failure") else message("success: ", foo)
# final failure
现在这对您来说更好,但它仍可能无意中在服务器上引入拒绝服务“攻击”。 (考虑一下“为什么”查询失败:如果是因为服务器暂时被淹没了,那么即使对于一些有限的请求,你也会通过破坏它来使事情变得更糟。)虽然它会减慢你的速度,但在繁忙的情况下服务器,暂停将减轻服务器的负担,并可能在失败之前为您提供更好的成功查询机会。
更好的解决方案
用网络术语来说,小的 TCP 数据包在重复重试时会导致拥塞(请参阅Nagle's Algorithm 以获得快速参考)。使用某种形式的指数退避很常见,为了防止两个(或更多)客户端同时执行完全相同的退避,一些客户端会轻微抖动(例如,httr::RETRY
)。
set.seed(2)
foo <- NULL
max_attempts <- 3
# borrowed from hadley/httr::RETRY
pause_cap <- pause_base <- 1
counter <- 0
while (is.null(foo) && counter < max_attempts)
if (counter > 0L)
length <- stats::runif(1, max = min(pause_cap, pause_base * (2 ^ counter)))
message("sleeping ", round(length, 1))
Sys.sleep(length)
counter <- counter + 1
foo <- tryCatch(
if (runif(1) < 0.9) stop("fail!") else 1
,
error = function(e) message("err"); NULL;
)
# err
# sleeping 0.7
# err
# sleeping 0.2
if (is.null(foo)) message("final failure") else message("success: ", foo)
# success: 1
总结
有些草率的代码,但我希望你明白这一点。在没有某种形式的自我限制的情况下将循环放在网络查询上很容易升级为无意的 DOS。
【讨论】:
我不知道如何回答 OP 问题 OP 询问 “每次 tryCatch 给我一个错误时如何重新执行循环”。这提供了一种在出现错误时执行循环的方法。我错过了什么吗? 不,知道了,这是一种复杂的方法恕我直言,我更倾向于使用处理程序来重新启动 tryCatch(处理此处理程序中的超时问题)。 你可能是对的。由于我不熟悉使用withRestarts
,所以这不是我的第一个首选解决方案(在 R 中)。也许您可以提供一个具有迭代限制和休眠的示例?
好吧,我会看看我是否有时间把它写好。必须使用相同的预防措施,但使用 bigrquery 是一个异步系统,因为您只是提交作业然后定期轮询以查看它是否已完成,因此不需要那么多。即:这里很少有机会 DOS 目标。但我完全同意必须强制执行一些重试,可能使用处理程序的test
参数(我通常不重试完全相同的代码,所以我之前测试过以确保它足够清楚)。 Better explained than I could do【参考方案3】:
根据您的想法,我创建了这段代码,它似乎有效 - 我只需要对其进行压力测试。
QFoo <- paste0('SQL Code', dateBQ, ' ')
BQFoo <- NULL
testfun <- function(QFoo)
withRestarts(
tryCatch(
query_exec(QFoo, "bigquery")
,
warning = function(e) invokeRestart("logger", level="warning", message = e) ,
error = function(e) invokeRestart("logger", level="error", message = e) )
,
logger = function(level, message)
message(date(), " [", level, "]: ", message[['message']])
)
testfun(QFoo)
【讨论】:
这不可能重试,这里没有对查询函数的回调,我的第二个例子是使用演示,不是试图回答你的问题。第一个示例更有可能对您有所帮助,使用query_exec
而不是 if (runif...
块。答案应该回答问题,所以不是论坛,如果你有东西要添加,编辑你的问题,然后最终用 @name ping 用户。
你给我的感觉是你确实在第一个点之后停止阅读我的评论。退后一步,再次完整地阅读答案,包括代码块之间的文本。以上是关于出错时再次循环的主要内容,如果未能解决你的问题,请参考以下文章