使用F#和Task <t>

时间:2017-07-10 16:27:20

标签: asynchronous concurrency f#

我有一个像这样的C#API:

Task<T> Foo(serverUri)

我们说我有4个可能的服务器。我想实现一个将返回DiscUnionBar类型的函数:

type DiscUnionBar of T =
    Safe of T | Weak of T | ConnectionError

实施将具有以下要求:

  1. 使用3个不同的serverUris对Foo()执行3次(最多)并发调用。
  2. 选择2个最快的成功回复。如果它们给出相同的结果T1和T2(T1 == T2),则停止执行并发请求并忽略/取消正在进行的请求并返回T的安全性。如果T1!= T2,继续执行更多请求(或查看响应)直到找到两个相同的答案。
  3. 如果任何请求失败(抛出ServerException),请尝试使用之前未请求的serverUri。
  4. 如果对所有4台服务器的所有请求都失败,则返回ConnectionError。
  5. 如果只有1个请求成功,则返回T的弱点。
  6. 这很容易做到,因为我不能使用F#的异步并且必须坚持使用C#的任务用法吗?我在这一点上有点迷失。

1 个答案:

答案 0 :(得分:6)

除非有理由不能在代码中使用Async ,否则唯一的限制是Foo必须返回Task,将调用Task产生的Foo转换为Async Foo应该没有问题。

这样你就可以使用F#的异步计算表达式来构建逻辑,好像Async返回了let simpleComputation serverUri = async { let! fooResult = Foo(serverUri) |> Async.AwaitTask (* here you can work with the T returned by Foo's task *) }

Task

我对Async.AwaitTask库也有很好的使用经验,可以直接在异步计算表达式中使用AwaitTask,而不必明确调用type TClientConnection = class(TIdServerContext) public Cache: TIdThreadSafeStringList; uuid: string; // or TGuid or whatever you are using... ForceDisconnect: Boolean; // <-- add this end; ... var List: TList; // or TIdContextList in modern Indy versions I: Integer; Client: TClientConnection; begin List := Server.Contexts.LockList; try for I := 0 to List.Count - 1 do begin Client := TClientConnection(TIdContext(List.Items[I])); if Client.uuid = idtodiscnnect then begin Client.ForceDisconnect := True; // <-- don't actually disconnect here, just signal it Break; end; end; finally Server.Contexts.UnlockList; end; end; ... procedure TMyForm.ServerConnect(AContext: TIdContext); begin (AContext as TClientConnection).LastSendRecv := Ticks64; AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8; AContext.Connection.IOHandler.ReadTimeout := 30000; end; procedure TMyForm.ServerExecute(AContext: TIdContext); var Client: TClientConnection; CMD: String; Cache, OutboundCmds: TStringList; I: integer; Len: Integer; begin Client := AContext as TClientConnection; if Client.ForceDisconnect then // <-- do the actual disconnect here begin AContext.Connection.Disconnect; Exit; end; Len := AContext.Connection.IOHandler.InputBuffer.Size; if Len >= 200000 then begin AContext.Connection.Disconnect; Exit; end; // check for pending outbound commands... OutboundCmds := nil; try Cache := Connection.OutboundCache.Lock; try if Cache.Count > 0 then begin OutboundCmds := TStringList.Create; OutboundCmds.Assign(Cache); Cache.Clear; end; finally Connection.OutboundCache.Unlock; end; if OutboundCmds <> nil then begin for I := 0 to OutboundCmds.Count - 1 do begin if Client.ForceDisconnect then // <-- and here, for good measure begin AContext.Connection.Disconnect; Exit; end; AContext.Connection.IOHandler.WriteLn(OutboundCmds.Strings[I]); end; Connection.LastSendRecv := Ticks64; end; finally if OutboundCmds <> nil then begin for I := 0 to OutboundCmds.Count - 1 do begin OutboundCmds.Objects[I].Free; end; end; OutboundCmds.Free; end; // check for a pending inbound command... if AContext.Connection.IOHandler.InputBufferIsEmpty then begin AContext.Connection.IOHandler.CheckForDataOnSource(100); AContext.Connection.IOHandler.CheckForDisconnect; if AContext.Connection.IOHandler.InputBufferIsEmpty then begin // if the client wants to stay connected, it should // send a command every so often... if GetElapsedTicks(Client.LastSendRecv) >= 30000 then begin AContext.Connection.Disconnect; Exit; end; end; end; CMD := AContext.Connection.IOHandler.ReadLn; Client.LastSendRecv := Ticks64; ... end; ,并且可以帮助实现异步/任务互操作一般。虽然有些人可能不喜欢它试图隐藏任务。