如果我要解决这样的一组要求,那么我要查看的工具是:
使用 .NET 核心工作者服务,有一个 C# 模板dotnet new worker -lang c# -o CSharpService
创建一个 C# 长时间运行的程序。可以在 F# 中创建相同的长时间运行的服务。
创建项目如下:
dotnet new console -lang F# -o FsharpService
cd FsharpService
dotnet add package Microsoft.Extensions.Hosting
dotnet add package Microsoft.Extensions.Hosting.WindowsServices
dotnet add package System.Net.NameResolution
然后将 Program.fs 替换为:
open System
open System.Threading.Tasks
open Microsoft.Extensions.DependencyInjection
open Microsoft.Extensions.Hosting
open Microsoft.Extensions.Logging
type Worker(logger : ILogger<Worker>) =
inherit BackgroundService()
let _logger = logger
override bs.ExecuteAsync stoppingToken =
let f : Async<unit> = async {
while not stoppingToken.IsCancellationRequested do
_logger.LogInformation("Worker running at: {time}", DateTime.Now)
do! Async.Sleep(1000)
}
Async.StartAsTask f :> Task
let CreateHostBuilder argv : IHostBuilder =
let builder = Host.CreateDefaultBuilder(argv)
builder.UseWindowsService()
.ConfigureServices(fun hostContext services -> services.AddHostedService<Worker>()
|> ignore<IServiceCollection>)
[<EntryPoint>]
let main argv =
let hostBuilder = CreateHostBuilder argv
hostBuilder.Build().Run()
0 // return an integer exit code
最后,如果您在 Windows 上构建注册并启动服务:
dotnet publish -r win-x64 -c Release /p:PublishSingleFile=true /p:Trimmed=true -o "./published"
sc create FsharpService binPath= "%cd%\published\FsharpService.exe"
services.msc
我的博客上有更多详细信息。
我要看的另一种技术是Task Parallel Library。这允许您构建一个工作流,其中部分或全部可以是并行的,但需要注意块之间的并发和消息传递。从 F# 和模型中调用很简单,其中每个模块都有一个输入类型和(在某些情况下)一个输出类型适合 F#设计类型方法。
这是我在第一次查看 TPL 和 F# 时放在一起的一个简单示例。注意:我没有机会运行它并确认它仍然有效,如果您尝试使用它,您还需要修改 #r 命令以在您的机器上工作。
#r @"System.Threading.Tasks.Dataflow.dll"
open System
open System.IO
open System.Threading.Tasks.Dataflow
let buildPropagateLinkOption () =
let mutable linkOption = new DataflowLinkOptions()
linkOption.PropagateCompletion <- true
linkOption
let buildParallelExecutionOption noThreads =
let mutable executionOption = new ExecutionDataflowBlockOptions()
executionOption.MaxDegreeOfParallelism <- noThreads
executionOption
type TPLRequest = {
path:string ;
filter:string ;
}
type TPLFile = {
fileName : string ;
}
type TPLResponse = {
fileName : string ;
size : int64 ;
}
let b1Impl (inReq:TPLRequest) : TPLFile seq =
printfn "Directory %s %A" inReq.path System.Threading.Thread.CurrentThread.ManagedThreadId
Directory.EnumerateFiles(inReq.path, inReq.filter) |> Seq.map(fun x -> {fileName = x})
let b2Impl (inReq:TPLFile) : TPLResponse =
let fInfo = FileInfo(inReq.fileName)
printfn "File %s %A" inReq.fileName System.Threading.Thread.CurrentThread.ManagedThreadId
{fileName = inReq.fileName; size = fInfo.Length }
let b3Impl (inReq:TPLResponse) =
printfn "%s %d %A" inReq.fileName inReq.size System.Threading.Thread.CurrentThread.ManagedThreadId
let buildFlow () =
let parallelExecutionOption = buildParallelExecutionOption 4
let b1 = new TransformManyBlock<TPLRequest,TPLFile>((fun x -> b1Impl x),parallelExecutionOption)
let b2 = new TransformBlock<TPLFile,TPLResponse>((fun x -> b2Impl x),parallelExecutionOption)
let b3 = new ActionBlock<TPLResponse>((fun x ->b3Impl x),parallelExecutionOption)
let propagateLinkOption = buildPropagateLinkOption ()
b1.LinkTo(b2,propagateLinkOption) |> ignore<IDisposable>
b2.LinkTo(b3,propagateLinkOption) |> ignore<IDisposable>
b1
let runFlow () =
let flow = buildFlow ()
flow.Post {path="C:\\temp"; filter = "*.txt"} |> ignore<bool>
flow.Post {path="C:\\temp"; filter = "*.zip"} |> ignore<bool>
flow.Complete()
flow.Completion.Wait()
()
runFlow ()