我已经写了这个f#echo服务器:
open System.Net
open System.Net.Sockets
open System.IO
open System
open System.Text
open System.Collections.Generic
let addr = IPAddress.Parse("127.0.0.1")
let listener = new TcpListener(addr, 2000)
listener.Start()
let rec loop2(c:TcpClient,sr:StreamReader,sw:StreamWriter)=async {
let line=sr.ReadLine()
if not(line=null) then
match line with
|"quit"->
sr.Close()
sw.Close()
c.Close()
|_ ->
if line.Equals("left") then
sw.WriteLine("right")
return! loop2(c,sr,sw)
sw.WriteLine(line)
return! loop2(c,sr,sw)
else
sr.Close()
sw.Close()
c.Close()
}
let loop()=async {
while true do
let c=listener.AcceptTcpClient()
let d = c.GetStream()
let sr = new StreamReader(d)
let sw = new StreamWriter(d)
sw.AutoFlush<-true
Async.Start(loop2(c,sr,sw))
}
Async.RunSynchronously(loop())
该程序可以:
但是当我运行编程时,当客户端发送&#39;离开&#39;正确&#39;并且发送&#39;退出&#39;时,我得到了这个例外:< / p>
未处理异常:System.ObjectDisposedException :(不要写 关闭)TextWriter。in Microsoft.FSharp.Control.CancellationTokenOps.Start@1192-1.Invoke(例外 e)in。$ Control.loop @ 419-40(Trampoline this, FSharpFunc
2 action) in Microsoft.FSharp.Control.Trampoline.ExecuteAction(FSharpFunc
2 firstAction)in Microsoft.FSharp.Control.TrampolineHolder.Protect(FSharpFunc`2 firstAction)in 。$ Control.-ctor @ 476-1.Invoke(对象状态)in System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(对象 国家) System.Threading.ExecutionContext.RunInternal(执行上下文 executionContext,ContextCallback回调,对象状态, BooleanpreserveSyncCtx)in System.Threading.ExecutionContext.Run(执行上下文 executionContext,ContextCallback回调,对象状态,布尔值 preserveSyncCtx)in System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() 在System.Threading.ThreadPoolWorkQueue.Dispatch()中 System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()。 。 。(按任意键继续)
Screenshot of program in action
Screenshot of exception
我该如何解决这个问题?
答案 0 :(得分:2)
问题在于,与命令式语言中的同名不同,计算表达式中的return!
不会短路。一旦if line.Equals("right")
中的if
返回,即。套接字关闭后,运行else
块后的代码并尝试写入已关闭的套接字。解决方法是将这两行放在 if line.Equals("left") then
sw.WriteLine("right")
return! loop2(c,sr,sw)
else
sw.WriteLine(line)
return! loop2(c,sr,sw)
:
match
作为一个额外的样式提示,这个整体可以实现为let rec loop2(c:TcpClient,sr:StreamReader,sw:StreamWriter)=async {
let line=sr.ReadLine()
match line with
| null | "quit" ->
sr.Close()
sw.Close()
c.Close()
| "left" ->
sw.WriteLine("right")
return! loop2(c,sr,sw)
| _ ->
sw.WriteLine(line)
return! loop2(c,sr,sw)
}
:
(echo dataExample) >> C:\filename.txt 2> nul
答案 1 :(得分:2)
您的代码中存在问题:
if line.Equals("left") then
sw.WriteLine("right")
return! loop2(c,sr,sw)
sw.WriteLine(line)
return! loop2(c,sr,sw)
如果&#34;离开&#34;收到,它写道&#34;对&#34;然后执行嵌套的loop2
直到&#34;退出&#34;收到了。然后,在完成所有这些操作之后,它会尝试编写line
并执行更多嵌套的loop2
。当然,到目前为止,您已经处理了连接,因此例外。
似乎写line
应该在else块中,这样可以防止错误:
if line.Equals("left") then
sw.WriteLine("right")
else
sw.WriteLine(line)
return! loop2(c,sr,sw)
当然,您也可以将此检查与模式匹配集成。下面的示例将在一个结构中处理空检查和每个字符串选项。
let line = Option.ofObj <| sr.ReadLine()
match line with
|None
|Some("quit") ->
sr.Close()
sw.Close()
|Some("left") ->
sw.WriteLine("right")
return! loop2(c,sr,sw)
|Some(line) ->
sw.WriteLine(line)
return! loop2(c,sr,sw)
请注意,您的async
数据块完全没用,因为您只使用了阻止功能,例如AcceptTcpClient()
,ReadLine()
和WriteLine()
。将这些函数放在async
块中并不会使它们异步。如果你想异步工作,它必须一直是异步的。
我猜你的目标是在客户到达时异步接受客户端,在不同的功能中异步处理每个客户端。
这个领域的大多数.NET API是用Task<'T>
而不是F#特定的async<'T>
编写的,所以我建议创建一些辅助函数:
let writeLineAsync (sw:StreamWriter) (text : string) =
sw.WriteLineAsync(text).ContinueWith(fun t -> ())
|> Async.AwaitTask
let readLineAsync (sr:StreamReader) =
sr.ReadLineAsync()
|> Async.AwaitTask
let acceptClientAsync (l : TcpListener) =
l.AcceptTcpClientAsync()
|> Async.AwaitTask
然后您可以创建一个正确的异步版本:
let rec handleClient (c:TcpClient) (sr:StreamReader) (sw:StreamWriter) =
async {
let! line = readLineAsync sr
match Option.ofObj(line) with
|None
|Some("quit")->
sr.Close()
sw.Close()
|Some("left") ->
do! writeLineAsync sw "right"
return! loop2(c,sr,sw)
|Some(line) ->
do! writeLineAsync sw line
return! loop2(c,sr,sw)
}
let loop() =
async {
let! c = acceptClientAsync listener
let sr = new StreamReader(c.GetStream())
let sw = new StreamWriter(c.GetStream())
sw.AutoFlush <- true
do! handleClient c sr sw |> Async.StartChild |> Async.Ignore
return! loop()
}