2
\$\begingroup\$

I've hacked together some code to read the content from a sequence of files:

let fileContents = 
    [ "filename1"; "filename2"; "etc" ]
    |> Seq.map(fun file -> async {
        use fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)
        let data = Array.create(int fs.Length) 0uy
        let! _ = fs.AsyncRead(data, 0, data.Length)
        return data         
    })
    |> Async.Parallel 
    |> Async.RunSynchronously
    |> Seq.map System.Text.Encoding.ASCII.GetString

It works, but I'm not really sure about it. For one thing, is async actually giving me any benefit here? Also inside the async function I need to wait for AsyncRead to finish, but I don't need the returned value from it. Is there a better way of doing this than let! _ = ...?

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

You can take advantage of async to do parallel IO which Async.Parallel makes very easy. Also, when you don't need the return value of an async call you can use Async.ignore in combination with do!: do! fs.AsyncRead(data, 0, data.Length) |> Async.ignore

However this specific snipper can be made much simpler since there is no reason to await the result of the computations when using Async.Parallel:

open System.IO

let fileContents = 
    [ "filename1"; "filename2"; "etc" ]
    |> List.map(fun file -> async { use fs = File.OpenRead(file) in return! fs.AsyncRead(fs.Length |> int)})
    |> Async.Parallel 
    |> Async.RunSynchronously
    |> Seq.map System.Text.Encoding.ASCII.GetString

One caveat - if the size of your file is more than 2GB it won't work due to long->int conversion - but that would be a crazy big file to just read the content of like that :-D

\$\endgroup\$
3
  • \$\begingroup\$ Cool tips. I was trying |> ignore instead of |> Async.Ignore which doesn't compile. Can you tell me what in is doing in let fs = File.OpenRead(file) in fs.AsyncRead(fs.Length |> int) and how the let binding is also returning a value please? \$\endgroup\$
    – NickL
    Commented Feb 25, 2014 at 21:46
  • \$\begingroup\$ Hi NickL, the in keyword is a way to bind a value as part of the next expression and is something f# inherited from OCaml. It's similar to doing a line-break after the let expression, but the value (in this case fs) is only available in the next expression. The let binding isn't returning a value but just binding the expression File.OpenRead(file) to it. Primary reason you would use in in f# is to skip a line-break and thus write a one-liner or if you already had a binding of the same name that you wanted to shadow only in the next expression on the same line. Hope this makes sense. \$\endgroup\$ Commented Feb 26, 2014 at 8:34
  • \$\begingroup\$ @NickL Btw, notice that I changed my response since my previous one actually was problematic since I never closed the stream. So now I use the async computation expression in order to be able to await the async read so that the use-expression will work and dispose the file-stream. It's still running it in parallel though. \$\endgroup\$ Commented Feb 26, 2014 at 8:47

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.