Po uruchomieniu poniższego testu (zbudowanego z F # 2.0) otrzymuję OutOfMemoryException. Osiągnięcie wyjątku w moim systemie zajmuje około 5 minut (i7-920 6 gb pamięci RAM, jeśli działał jako proces x86), ale w każdym razie możemy zobaczyć, jak pamięć rośnie w menedżerze zadań.Czy Async.StartChild ma wyciek pamięci?
module start_child_test
open System
open System.Diagnostics
open System.Threading
open System.Threading.Tasks
let cnt = ref 0
let sw = Stopwatch.StartNew()
Async.RunSynchronously(async{
while true do
let! x = Async.StartChild(async{
if (Interlocked.Increment(cnt) % 100000) = 0 then
if sw.ElapsedMilliseconds > 0L then
printfn "ops per sec = %d" (100000L*1000L/sw.ElapsedMilliseconds)
else
printfn "ops per sec = INF"
sw.Restart()
GC.Collect()
})
do! x
})
printfn "done...."
Nie widzę nic złego w tym kodzie, a nie widzę żadnych powodów, dla pamięci rośnie. Zrobiłem alternatywną implementację aby upewnić się, że moje argumenty są ważne:
module start_child_fix
open System
open System.Collections
open System.Collections.Generic
open System.Threading
open System.Threading.Tasks
type IAsyncCallbacks<'T> = interface
abstract member OnSuccess: result:'T -> unit
abstract member OnError: error:Exception -> unit
abstract member OnCancel: error:OperationCanceledException -> unit
end
type internal AsyncResult<'T> =
| Succeeded of 'T
| Failed of Exception
| Canceled of OperationCanceledException
type internal AsyncGate<'T> =
| Completed of AsyncResult<'T>
| Subscribed of IAsyncCallbacks<'T>
| Started
| Notified
type Async with
static member StartChildEx (comp:Async<'TRes>) = async{
let! ct = Async.CancellationToken
let gate = ref AsyncGate.Started
let CompleteWith(result:AsyncResult<'T>, callbacks:IAsyncCallbacks<'T>) =
if Interlocked.Exchange(gate, Notified) <> Notified then
match result with
| Succeeded v -> callbacks.OnSuccess(v)
| Failed e -> callbacks.OnError(e)
| Canceled e -> callbacks.OnCancel(e)
let ProcessResults (result:AsyncResult<'TRes>) =
let t = Interlocked.CompareExchange<AsyncGate<'TRes>>(gate, AsyncGate.Completed(result), AsyncGate.Started)
match t with
| Subscribed callbacks ->
CompleteWith(result, callbacks)
| _ ->()
let Subscribe (success, error, cancel) =
let callbacks = {
new IAsyncCallbacks<'TRes> with
member this.OnSuccess v = success v
member this.OnError e = error e
member this.OnCancel e = cancel e
}
let t = Interlocked.CompareExchange<AsyncGate<'TRes>>(gate, AsyncGate.Subscribed(callbacks), AsyncGate.Started)
match t with
| AsyncGate.Completed result ->
CompleteWith(result, callbacks)
| _ ->()
Async.StartWithContinuations(
computation = comp,
continuation = (fun v -> ProcessResults(AsyncResult.Succeeded(v))),
exceptionContinuation = (fun e -> ProcessResults(AsyncResult.Failed(e))),
cancellationContinuation = (fun e -> ProcessResults(AsyncResult.Canceled(e))),
cancellationToken = ct
)
return Async.FromContinuations(fun (success, error, cancel) ->
Subscribe(success, error, cancel)
)
}
Do tego testu to działa dobrze bez zasadniczo między zużyciem pamięci. Niestety nie mam dużego doświadczenia w F # i mam wątpliwości, czy tęsknię za niektórymi rzeczami. Jeśli jest to błąd, jak mogę zgłosić to zespołowi F #?
Czy wiesz, dlaczego jest to konieczne? Dlaczego utworzono nowy "CTS"? Czy nie wystarczy użycie oryginalnego 'ct'? – svick
@svick - Dobre pytanie. Myślę, że wewnętrzny token anulujący jest używany do obsługi limitu czasu, który można określić dla 'StartChild' (ten limit czasu nie powinien anulować obliczeń, które nazywają' StartChild', chyba że faktycznie czekasz na wynik później). –
Nie pomyślałem o tym. Tak, to ma sens. – svick