我正在使用f#运行一个简单的聊天应用程序。在聊天中,当一个用户键入“退出”时,我希望两个客户端都结束聊天。当前,我正在控制台中运行,因此读写受到阻碍,但是我使用了一个类来包装控制台,因此没有异步问题。
(在下面的代码中,sendUI和reciveUI是异步函数,它们通过网络发送和接收消息)
type IConnection =
abstract Send : string -> Async<bool>
abstract Recieve : unit -> Async<string>
abstract Connected : bool
abstract Close : unit -> unit
type IOutput =
abstract ClearLine : unit -> unit
abstract ReadLine : ?erase:bool -> string
abstract WriteLine : string -> unit
let sendUI (outputer:#IOutput) (tcpConn: #IConnection) () =
async {
if not tcpConn.Connected then return false
else
let message = outputer.ReadLine(true)
try
match message with
| "exit" -> do! tcpConn.Send "exit" |> Async.Ignore
return false
| _ -> if message.Trim() <> ""
then do! message.Trim() |> tcpConn.Send |> Async.Ignore
outputer.WriteLine("me: " + message)
return true
with
| e -> outputer.WriteLine("log: " + e.Message)
return false
}
let recieveUI (outputer:#IOutput) (tcpConn: #IConnection) () =
async {
if not tcpConn.Connected then return false
else
try
let! response = tcpConn.Recieve()
match response with
| "exit" -> return false
| _ -> outputer.WriteLine("other: " + response)
return true
with
| e -> outputer.WriteLine("error: " + e.Message)
return false
}
let rec loop (cancel:CancellationTokenSource) f =
async {
match! f() with
| false -> cancel.Cancel(true)
| true -> do! loop cancel f
}
let messaging recieve send (outputer: #IOutput) (tcpConn:#IConnection) =
printfn "write: exit to exit"
use cancelSrc = new CancellationTokenSource()
let task =
[ recieve outputer tcpConn
send outputer tcpConn ]
|> List.map (loop cancelSrc)
|> Async.Parallel
|> Async.Ignore
try
Async.RunSynchronously (computation=task, cancellationToken=cancelSrc.Token)
with
| :? OperationCanceledException ->
tcpConn.Close()
let exampleReceive =
{ new IConnection with
member this.Connected = true
member this.Recieve() = async { do! Async.Sleep 1000
return "exit" }
member this.Send(arg1) = async { return true }
member this.Close() = ()
}
let exampleOutputer =
{ new IOutput with
member this.ClearLine() = raise (System.NotImplementedException())
member this.ReadLine(erase) = Console.ReadLine()
member this.WriteLine(arg) = Console.WriteLine(arg) }
[<EntryPoint>]
let main args =
messaging recieveUI sendUI exampleOutputer exampleReceive
0
(我用一个对象包裹了控制台,所以我不会在屏幕上看到奇怪的东西:输出器)
当我通过线路“退出”时,我返回false,因此循环调用取消,因此它也应该停止发送消息异步计算。
但是,当我这样做时,sendUI卡住了:
async {
//do stuff
let message = Console.ReadLine() //BLOCKS! doesn't cancel
//do stuff
}
一种解决方法是以某种方式使Console.ReadLine()异步,但是简单的异步{return ...}不起作用。
我还尝试将其作为任务运行并调用Async.AwaitTask,但这也不起作用!
我读到一个人可以使用Async.FromContinuations,但是我不知道如何使用它(而我尝试过的方法并没有解决它...)
没有什么帮助?
编辑
之所以不能简单地工作,是因为异步计算取消的工作方式。他们检查是否在放手/完成/返回时取消。等等,因此上述解决方案不起作用。
编辑2
添加了可运行的代码示例
答案 0 :(得分:0)
您可以将Console.ReadLine
包裹在自己的async
中,然后用Async.RunSynchronously
和CancellationToken
进行调用。这将允许您取消该阻止操作,因为它与控制台本身不在同一线程上。
open System
open System.Threading
type ITcpConnection =
abstract member Send: string -> unit
let readLineAsync cancellation =
async {
try
return Some <| Async.RunSynchronously(async { return Console.ReadLine() }, cancellationToken = cancellation)
with | _ ->
return None
}
let receiveUI cancellation (tcpConnection: ITcpConnection) =
let rec loop () =
async {
let! message = readLineAsync cancellation
match message with
| Some msg -> msg |> tcpConnection.Send
| None -> printfn "Chat Session Ended"
return! loop ()
}
loop () |> Async.Start