2012-03-21 22 views
0

我是F#的新手,我已經在下面的代碼中列出了我在網上找到的各種示例以試圖更好地理解如何使用它。目前,下面的代碼從文件中讀取機器列表並且ping每臺機器。我必須將文件中的初始數組分成25個較小的機器數組,以控制並發動作的數量,否則需要花費很長時間才能映射出機器列表。我想能夠使用線程池來管理線程,但我還沒有找到一種方法來使其工作。任何指導都會很棒。我無法使這項工作:F# - 需要幫助將其轉換爲使用線程池

let creatework = FileLines|> Seq.map (fun elem -> ThreadPool.QueueUserWorkItem(new WaitCallback(dowork), elem)) 

下面是完整的代碼:

open System.Threading 
open System 
open System.IO 

let filePath = "c:\qa\machines.txt" 

let FileLines = File.ReadAllLines(filePath) 

let count = FileLines.Length/25 

type ProcessResult = { exitCode : int; stdout : string; stderr : string } 

let executeProcess (exe,cmdline) = 
    let psi = new System.Diagnostics.ProcessStartInfo(exe,cmdline) 
    psi.UseShellExecute <- false 
    psi.RedirectStandardOutput <- true 
    psi.RedirectStandardError <- true 
    psi.CreateNoWindow <- true 
    let p = System.Diagnostics.Process.Start(psi, EnableRaisingEvents = true) 
    let output = new System.Text.StringBuilder() 
    let error = new System.Text.StringBuilder() 
    p.OutputDataReceived.Add(fun args -> output.AppendLine(args.Data)|> ignore) 
    p.ErrorDataReceived.Add(fun args -> error.AppendLine(args.Data) |> ignore) 
    p.BeginErrorReadLine() 
    p.BeginOutputReadLine() 
    p.WaitForExit() 
    { exitCode = p.ExitCode; stdout = output.ToString(); stderr = error.ToString() } 

let dowork machinename= 
    async{ 
     let exeout = executeProcess(@"c:\windows\system32\ping.exe", "-n 1 " + machinename) 
     let exelines = 
      if exeout.stdout.Contains("Reply from") then Console.WriteLine(machinename + " " + "REPLY") 
      elif exeout.stdout.Contains("Request timed out.") then Console.WriteLine(machinename + " " + "RTO") 
      elif exeout.stdout.Contains("Ping request could not find host") then Console.WriteLine(machinename + " " + "Unknown Host") 
      else Console.WriteLine(machinename + " " + "ERROR") 
     exelines 
     } 

printfn "%A" (System.DateTime.Now.ToString()) 

for i in 0..count do 
    let x = i*25 
    let y = if i = count then FileLines.Length-1 else (i+1)*25 
    printfn "%s %d" "X equals: " x 
    printfn "%s %d" "Y equals: " y 
    let filesection = FileLines.[x..y] 
    let creatework = filesection |> Seq.map dowork |> Async.Parallel |> Async.RunSynchronously|>ignore 
    creatework 

printfn "%A" (System.DateTime.Now.ToString()) 
printfn "finished" 

UPDATE: 以下作品中的代碼,並提供了一個框架,我想做的事情。 Tomas Petricek引用的鏈接確實具有使這項工作成功的代碼位。我只需要確定哪個例子是正確的。在用Java編寫的重複框架的3秒鐘內,所以我認爲我正朝着正確的方向前進。我希望下面的例子將是有益的任何人試圖擰出F#的各種可執行文件:

open System 
open System.IO 
open System.Diagnostics 

let filePath = "c:\qa\machines.txt" 

let FileLines = File.ReadAllLines(filePath) 

type Process with 
    static member AsyncStart psi = 
     let proc = new Process(StartInfo = psi, EnableRaisingEvents = true) 
     let asyncExit = Async.AwaitEvent proc.Exited 
     async { 
      proc.Start() |> ignore 
      let! args = asyncExit 
      return proc 
     } 

let shellExecute(program : string, args : string) = 
    let startInfo = 
     new ProcessStartInfo(FileName = program, Arguments = args, 
      UseShellExecute = false, 
      CreateNoWindow = true, 
      RedirectStandardError = true, 
      RedirectStandardOutput = true) 
    Process.AsyncStart(startInfo) 

let dowork (machinename : string)= 
    async{ 
     let nonbtstat = "NONE" 
     use! pingout = shellExecute(@"c:\windows\system32\ping.exe", "-n 1 " + machinename) 
     let pingRdToEnd = pingout.StandardOutput.ReadToEnd() 
     let pingresults = 
      if pingRdToEnd.ToString().Contains("Reply from") then (machinename + " " + "REPLY") 
      elif pingRdToEnd.ToString().Contains("Request timed out.") then (machinename + " " + "RTO") 
      elif pingRdToEnd.ToString().Contains("Ping request could not find host") then (machinename + " " + "Unknown Host") 
      else (machinename + " " + "PING_ERROR") 
     if pingresults.ToString().Contains("REPLY") then 
      use! nbtstatout = shellExecute(@"c:\windows\system32\nbtstat.exe", "-a " + machinename) 
      let nbtstatRdToEnd = nbtstatout.StandardOutput.ReadToEnd().Split('\n') 
      let nbtstatline = Array.tryFind(fun elem -> elem.ToString().Contains("<00> UNIQUE  Registered")) nbtstatRdToEnd 
      return Console.WriteLine(pingresults + nbtstatline.Value.ToString()) 
     else return Console.WriteLine(pingresults + " " + nonbtstat) 
     } 

printfn "%A" (System.DateTime.Now.ToString()) 

let creatework = FileLines |> Seq.map dowork |> Async.Parallel |> Async.RunSynchronously|>ignore 
creatework 

printfn "%A" (System.DateTime.Now.ToString()) 
printfn "finished" 
+0

相反炮擊了對'的ping.exe'有你使用[Ping類]考慮(http://msdn.microsoft.com/en-us/library/system.net.networkinformation.ping.aspx)而不是?通過一些工作,你可以將'SendAsync'方法和'PingCompleted'事件整合到一個F#異步工作流程中...... – 2012-03-21 21:41:31

+0

我之所以沒有這樣做是因爲運行ping只是爲了測試。我對F#的興趣是通過並行運行線程來利用多個CPU的能力。我工作的環境使用超過15年的無法更新的可執行文件,我想找到一種方法來利用多個核心,同時仍然保持向後兼容。 – 2012-03-22 18:11:49

回答

6

與您的代碼的主要問題是:executeProcess是同步的功能,需要一個很長的時間來運行(它運行ping.exe進程並等待其結果)。一般的規則是線程池中的任務不應該被阻塞很長時間(因爲它們會阻塞線程池線程,這意味着線程池不能有效地調度其他工作)。

我認爲你可以通過使executeProcess異步來解決這個問題。而不是調用WaitForExit(該塊),則可以使用Async.AwaitEvent等待Exitted事件:

let executeProcess (exe,cmdline) = async { 
    let psi = new System.Diagnostics.ProcessStartInfo(exe,cmdline) 
    psi.UseShellExecute <- false 
    // [Lots of stuff omitted] 
    p.BeginOutputReadLine() 
    let! _ = Async.AwaitEvent p.Exited 
    return { exitCode = p.ExitCode 
      stdout = output.ToString(); stderr = error.ToString() } } 

這應該解除線程的線程池,這樣你就可以從所有的URL使用Async.Parallel輸入數組,無需任何手動調度。

編輯作爲@desco在評論中指出,如果達到AwaitEvent前行的進程退出(之前可能會錯過的事件)以上是不完全正確。爲了解決這個問題,你需要使用Event.guard功能,這在本SO問題討論:

+1

Async.AwaitEvent p。退出可能會永久掛起,如果進程將很快退出(基本上是在調用AwaitEvent之前) – desco 2012-03-21 17:28:51

+0

我做出了有關更改執行功能的建議更改,但返回似乎並未從命令中捕獲結果。搜索「exeout」轉換爲字符串似乎沒有返回。它也運行得如此之快,我不相信它現在運行ping.exe。 – 2012-03-21 19:38:03

+0

@desco - 這是之前討論過的,但我找不到前面的問題。感謝提醒我 - 我添加了一個鏈接到答案,這應該解釋如何解決這個問題。 – 2012-03-21 23:14:03