在F#中,当你要处理,你几乎普遍希望使用option
或Choice<_,_>
型可恢复错误。实际上,它们之间的唯一区别是,Choice
允许您返回有关错误的一些信息,而option
则不允许。换句话说,option
是最好的时候它并不重要如何或为什么东西失败(只有它确实失败); Choice<_,_>
当有关于如何或为什么信息失败是重要的。例如,您可能想要将错误信息写入日志;或者您想要根据以不同的方式处理错误情况,为什么某些事情失败 - 这是一个很好的用例,它提供了准确的错误消息来帮助用户诊断问题。
考虑到这一点,这里就是我会重构代码来处理干净,实用的风格故障:
open System
open System.Net
/// Retrieves the content at the given URI.
let retrievePage (client : WebClient) (uri : Uri) =
// Preconditions
checkNonNull "uri" uri
if not <| uri.IsAbsoluteUri then
invalidArg "uri" "The URI must be an absolute URI."
try
// If the data is retrieved successfully, return it.
client.DownloadData uri
|> Choice1Of2
with
| :? System.Net.WebException as webExn ->
// Return the URI and WebException so they can be used to diagnose the problem.
Choice2Of2 (uri, webExn)
| _ ->
// Reraise any other exceptions -- we don't want to handle them here.
reraise()
/// Retrieves the content at the given URI.
/// If a WebException is raised when retrieving the content, the request
/// will be retried up to a specified number of times.
let rec retrievePageRetry (retryWaitTime : TimeSpan) remainingRetries (client : WebClient) (uri : Uri) =
// Preconditions
checkNonNull "uri" uri
if not <| uri.IsAbsoluteUri then
invalidArg "uri" "The URI must be an absolute URI."
elif remainingRetries = 0u then
invalidArg "remainingRetries" "The number of retries must be greater than zero (0)."
// Try to retrieve the page.
match retrievePage client uri with
| Choice1Of2 _ as result ->
// Successfully retrieved the page. Return the result.
result
| Choice2Of2 _ as error ->
// Decrement the number of retries.
let retries = remainingRetries - 1u
// If there are no retries left, return the error along with the URI
// for diagnostic purposes; otherwise, wait a bit and try again.
if retries = 0u then error
else
// NOTE : If this is modified to use 'async', you MUST
// change this to use 'Async.Sleep' here instead!
System.Threading.Thread.Sleep retryWaitTime
// Try retrieving the page again.
retrievePageRetry retryWaitTime retries client uri
[<EntryPoint>]
let main argv =
/// WebClient used for retrieving content.
use wc = new WebClient()
/// The amount of time to wait before re-attempting to fetch a page.
let retryWaitTime = TimeSpan.FromSeconds 2.0
/// The maximum number of times we'll try to fetch each page.
let maxPageRetries = 3u
/// The URI to fetch.
let fullURI = Uri ("http://www.badaddress.xyz", UriKind.Absolute)
// Fetch the page data.
match retrievePageRetry retryWaitTime maxPageRetries wc fullURI with
| Choice1Of2 pageData ->
printfn "Retrieved %u bytes from: %O" (Array.length pageData) fullURI
0 // Success
| Choice2Of2 (uri, error) ->
printfn "Unable to retrieve the content from: %O" uri
printfn "HTTP Status: (%i) %O" (int error.Status) error.Status
printfn "Message: %s" error.Message
1 // Failure
基本上,我拆你的代码伸到两个功能,加上原来main
:
- 一个函数,试图从指定的URI检索内容。
- 一个包含重试尝试逻辑的函数;这'包装'执行实际请求的第一个函数。
- 原始主要功能现在只处理'设置'(您可以轻松从
app.config
或web.config
中提取)并打印最终结果。换句话说,它忽略了重试逻辑 - 您可以使用match
语句修改单行代码,并根据需要使用非重试请求功能。
如果你想从多个URI拉内容,等待的时间显著量(例如5分钟)重试之间,你应该修改重试逻辑使用优先级队列或东西,而不是使用Thread.Sleep
或Async.Sleep
。
无耻的插件:我的ExtCore库包含一些东西,当你构建这样的事情时,你的生活将变得更加容易,特别是如果你想让它变得异步。最重要的是,它提供了一个asyncChoice
工作流程和collections functions designed to work with it。
至于你关于传入参数的问题(比如重试超时和重试次数) - 我不认为有一个确定是否通过它们或硬编码它们的硬性规则功能。在大多数情况下,我更愿意将它们传入,但如果您有超过几个参数传入,您最好创建一个记录来保存它们并传递它们。我使用的另一种方法是创建参数option
的值,其中默认值是从配置文件中提取的(尽管您想从文件中将它们拉出一次,并将它们分配给某个专用字段以避免重新解析每次你的函数被调用时,配置文件);这可以很容易地修改您在代码中使用的默认值,但也可以在必要时灵活地覆盖它们。