2011-10-28 4 views
1

저는 지난 몇 달 동안 F #을 배우려고 노력해 왔고 계속 저를 괴롭히는 무언가로 뛰어 들고 있습니다. 내 "학습 프로젝트"는 조작에 관심이있는 데이터에 대한 화면 스크래퍼입니다.응답 스트림 비싼 비동기 읽기

F # PowerPack에는 Stream.AsyncReadToEnd가 호출됩니다. 단 한 번의 통화에만 PowerPack을 사용하고 싶지 않았으므로 어떻게했는지 살펴 보았습니다.

module Downloader = 
    open System 
    open System.IO 
    open System.Net 
    open System.Collections 

    type public BulkDownload(uriList : IEnumerable) = 
     member this.UriList with get() = uriList 

     member this.ParalellDownload() = 
      let Download (uri : Uri) = async { 
       let UnblockViaNewThread f = async { 
        do! Async.SwitchToNewThread() 
        let res = f() 
        do! Async.SwitchToThreadPool() 
        return res } 

       let request = HttpWebRequest.Create(uri) 
       let! response = request.AsyncGetResponse() 
       use responseStream = response.GetResponseStream() 
       use reader = new StreamReader(responseStream) 
       let! contents = UnblockViaNewThread (fun() -> reader.ReadToEnd()) 
       return uri, contents.ToString().Length } 

      this.UriList 
      |> Seq.cast 
      |> Seq.map Download 
      |> Async.Parallel 
      |> Async.RunSynchronously 

그들은 UnblockViaNewThread 기능을 가지고 있습니다. 실제로 비동기 적으로 응답 스트림을 읽는 유일한 방법입니까? 정말 새로운 스레드를 만드는 것은 비싸지 않습니다. ("1 메가 바이트의 메모리"가 모든 곳에 던져진 것을 보았습니다.) 이 작업을 수행하는 더 좋은 방법이 있습니까? 이것은 Async* 전화 (실제로는 let! 일 수 있음)에서 실제로 발생하는 것입니까?

편집 : 나는 Tomas의 제안을 따르고 실제로 F # PowerTools와 별개의 것을 만들었습니다. 여기있어. 이것은 실제로 오류 처리가 필요하지만 비동기 요청을하고 바이트 배열에 URL을 다운로드합니다.

namespace Downloader 
open System 
open System.IO 
open System.Net 
open System.Collections 

type public BulkDownload(uriList : IEnumerable) = 
    member this.UriList with get() = uriList 

    member this.ParalellDownload() =     
     let Download (uri : Uri) = async { 
      let processStreamAsync (stream : Stream) = async { 
       let outputStream = new MemoryStream() 
       let buffer = Array.zeroCreate<byte> 0x1000 
       let completed = ref false 
       while not (!completed) do 
        let! bytesRead = stream.AsyncRead(buffer, 0, 0x1000) 
        if bytesRead = 0 then 
         completed := true 
        else 
         outputStream.Write(buffer, 0, bytesRead) 
       stream.Close() 
       return outputStream.ToArray() } 

      let request = HttpWebRequest.Create(uri) 
      let! response = request.AsyncGetResponse() 
      use responseStream = response.GetResponseStream() 
      let! contents = processStreamAsync responseStream 
      return uri, contents.Length } 

     this.UriList 
     |> Seq.cast 
     |> Seq.map Download 
     |> Async.Parallel 
     |> Async.RunSynchronously 

    override this.ToString() = String.Join(", ", this.UriList) 

답변

9

난 그냥 기적으로 별도의 스레드에 ReadToEnd를 호출 AsyncReadToEnd 잘못이라고 생각합니다.

F # PowerPack에는 스트림 읽기의 적절한 비동기 구현을 포함하는 AsyncStreamReader 형식이 포함되어 있습니다. 그것은 ReadLine 메서드를 (비동기 적으로) 다음 줄을 반환하고 (백그라운드 스레드에서 실행되는 것과는 반대로 비동기 ReadAsync을 사용하여) 원본 스트림에서 몇 개의 청크 만 다운로드합니다. 당신이 (대신에게 줄 단위의 처리) 문자열로 전체 콘텐츠를 다운로드하려면

let processStreamAsync stream = async { 
    use asyncReader = new AsyncStreamReader(stream) 
    let completed = ref false 
    while not (!completed) do 
    // Asynchrnously get the next line 
    let! nextLine = asyncReader.ReadLine() 
    if nextLine = null then completed := true 
    else 
     (* process the next line *) } 

, 당신은 AsyncStreamReaderReadToEnd 방법을 사용할 수 있습니다. 이것은 데이터 블록을 비동기 적으로 다운로드하기 시작하고 차단없이이를 반복하는 적절한 비동기 구현입니다.

async { 
    use asyncReader = new AsyncStreamReader(stream) 
    return! asyncReader.ReadToEnd() } 

또한, F 번호의 파워팩 오픈 souorce하고 허용 라이센스를 가지고, 그래서 그것을 사용하는 가장 좋은 방법은 당신이 프로젝트에 필요한 몇 개의 파일을 복사하는 것이 있습니다.

+1

내 질문에 완전히 대답합니다. 고마워요 토마스. –