我正在尝试实现我从Don Syme的博客中读到的模式
这表明利用异步I / O可以大幅提升性能。我目前正在尝试使用Array.Parallel.Map以一种方式“工作”一段代码,看看我是否可以使用Async.Parallel以某种方式实现相同的结果,但我真的不明白Async.Parallel,并且无法获得任何工作。
我有一段代码(下面简化说明这一点)成功检索一个cusip的数据数组。 (例如价格系列)
let getStockData cusip =
let D = DataProvider()
let arr = D.GetPriceSeries(cusip)
return arr
let data = Array.Parallel.map (fun x -> getStockData x) stockCusips
因此,这种方法构建了一个数组阵列,通过互联网连接到我的数据供应商,每个股票(可能多达3000个)并返回一个数组(每个库存1个,价格合理)每个系列)。我当然不明白Array.Parallel.map下面发生了什么,但是我想知道这是否是浪费资源的情况,并且它实际上可以更快地使用异步I / O?所以为了测试这个,我试图使用asyncs来创建这个函数,我认为下面的函数遵循Don Syme使用URL的文章中的模式,但它不会使用“let!”进行编译。
let getStockDataAsync cusip =
async { let D = DataProvider()
let! arr = D.GetData(cusip)
return arr
}
我得到的错误是: 该表达式应该具有类型Async<'a>但这里有类型obj
使用“let”而不是“let!”进行编译很好,但我认为整点都是你需要感叹号才能让命令在不阻塞线程的情况下运行。
所以第一个问题确实是,上面我的语法有什么问题,在getStockDataAsync中,然后在更高的层次,任何人都可以提供一些关于异步I / O的额外见解,以及我提出的场景是否会从中受益,使它可能比Array.Parallel.map更快,更快?非常感谢。
答案 0 :(得分:20)
F#异步工作流允许您实现异步计算,但是,F#区分了常规计算和异步计算。这种差异由类型系统跟踪。例如,下载网页并且是同步的方法具有类型string -> string
(获取URL并返回HTML),但是异步执行相同操作的方法具有类型string -> Async<string>
。在async
块中,您可以使用let!
来调用异步操作,但必须使用let
调用所有其他(标准同步)方法。现在,您的示例的问题是GetData
操作是普通的同步方法,因此您无法使用let!
调用它。
在典型的F#场景中,如果要使GetData
成员异步,则需要使用异步工作流实现它,因此您还需要将其包装在{{1}中阻止。在某些时候,您将到达一个您真正需要异步运行某些原始操作的位置(例如,从网站下载数据)。 F#提供了几个原始异步操作,您可以使用async
从async
块调用,例如let!
(AsyncGetResponse
方法的异步版本)。因此,在您的GetResponse
方法中,您将编写如下内容:
GetData
摘要是您需要识别一些原始异步操作(例如等待Web服务器或文件系统),在此时使用原始异步操作并在{{1中包装使用这些操作的所有代码块。如果没有可以异步运行的基本操作,那么您的代码是受CPU限制的,您可以使用let GetData (url:string) = async {
let req = WebRequest.Create(url)
let! rsp = req.AsyncGetResponse()
use stream = rsp.GetResponseStream()
use reader = new System.IO.StreamReader(stream)
let html = reader.AsyncReadToEnd()
return CalculateResult(html) }
。
我希望这可以帮助您了解F#异步工作流程的工作原理。有关详细信息,您可以查看Don Syme's blog post,关于asynchronous programming by Robert Pickering的系列或F# web cast。
答案 1 :(得分:6)
F#asyncs的习惯用法是用“Async”前缀(AsyncFoo
,而不是FooAsync
命名方法;后者是另一种.NET技术已经使用的习惯用法。因此,您的功能应为getStockData
和asyncGetStockData
。
在异步工作流程中,无论何时使用let!
代替let
或do!
代替do
,右边的内容都应为Async<T>
类型而不是T
。基本上,您需要一个现有的异步计算,以便在工作流程中的此时“异步”。每个Async<T>
本身都是其他async{...}
工作流程,或者是异步“原语”。基元在F#库中定义,或者通过Async.FromBeginEnd
或Async.FromContinuations
在用户代码中创建,这样可以定义开始计算的低级细节,注册I / O回调,释放线程,以及然后在回叫时重新启动计算。因此,为了获得异步I / O的全部好处,您必须将“异步”同步到一些真正的异步I / O原语。