Mi chiedo se questa è una domanda troppo ampia, ma recentemente mi sono fatto per trovare un pezzo di codice su cui vorrei essere certo su come tradurre da C# in F # corretto. Il viaggio inizia da here (1) (il problema originale con l'interazione TPL-F #) e continua here (2) (codice di esempio che sto pensando di tradurre in F #).Traduzione di codice C# da asincrona a F # rispetto allo schedulatore
Il codice di esempio è troppo lungo per essere riprodotto qui, ma le interessanti funzioni sono ActivateAsync
, RefreshHubs
e AddHub
. In particolare i punti interessanti sono
AddHub
ha una firma delprivate async Task AddHub(string address)
.RefreshHubs
chiamaAddHub
in un ciclo e raccoglie un elenco ditasks
, che attende poi alla fine daawait Task.WhenAll(tasks)
e di conseguenza il valore di ritorno corrisponde la sua firma diprivate async Task RefreshHubs(object _)
.RefreshHubs
viene chiamato daActivateAsync
comeawait RefreshHubs(null)
e quindi alla fine c'è una chiamataawait base.ActivateAsync()
corrispondente alla firma della funzionepublic override async Task ActivateAsync()
.
Domanda:
Quale sarebbe la traduzione corretta di tali firme funzione di F #, che conserva ancora l'interfaccia e le funzionalità e rispetta il valore di default, di pianificazione su misura? E non sono altrettanto sicuro di questo "async/await in F #". Come in come farlo "meccanicamente". :)
Il motivo è che nel collegamento "qui (1)" sembra esserci un problema (non l'ho verificato) in cui le operazioni asincrone F # non rispettano uno scheduler personalizzato, cooperativo impostato da (Orleans) runtime. Inoltre, è indicato here che le operazioni TPL sfuggono all'utilità di pianificazione e passano al pool di attività e il loro utilizzo è pertanto proibito.
Un modo che posso pensare di affrontare questo è con una funzione F # come segue
//Sorry for the inconvenience of shorterned code, for context see the link "here (1)"...
override this.ActivateAsync() =
this.RegisterTimer(new Func<obj, Task>(this.FlushQueue), null, TimeSpan.FromMilliseconds(100.0), TimeSpan.FromMilliseconds(100.0)) |> ignore
if RoleEnvironment.IsAvailable then
this.RefreshHubs(null) |> Async.awaitPlainTask |> Async.RunSynchronously
else
this.AddHub("http://localhost:48777/") |> Async.awaitPlainTask |> Async.RunSynchronously
//Return value comes from here.
base.ActivateAsync()
member private this.RefreshHubs(_) =
//Code omitted, in case mor context is needed, take a look at the link "here (2)", sorry for the inconvinience...
//The return value is Task.
//In the C# version the AddHub provided tasks are collected and then the
//on the last line there is return await Task.WhenAll(newHubAdditionTasks)
newHubs |> Array.map(fun i -> this.AddHub(i)) |> Task.WhenAll
member private this.AddHub(address) =
//Code omitted, in case mor context is needed, take a look at the link "here (2)", sorry for the inconvinience...
//In the C# version:
//...
//hubs.Add(address, new Tuple<HubConnection, IHubProxy>(hubConnection, hub))
//}
//so this is "void" and could perhaps be Async<void> in F#...
//The return value is Task.
hubConnection.Start() |> Async.awaitTaskVoid |> Async.RunSynchronously
TaskDone.Done
La funzione startAsPlainTask
è di Sacha Barber da here. Un'altra opzione interessante potrebbe essere here come
module Async =
let AwaitTaskVoid : (Task -> Async<unit>) =
Async.AwaitIAsyncResult >> Async.Ignore
< edit: ho appena notato il Task.WhenAll
avrebbe bisogno di essere atteso troppo. Ma quale sarebbe il modo corretto? Uh, il tempo di dormire (un brutto gioco di parole) ...
< Edit 2: Al here (1) (il problema originale con TPL-F # interazione) a Codeplex è stato detto che F # usa contesti di sincronizzazione per spingere lavoro per discussioni , mentre TPL no. Ora, questa è una spiegazione plausibile, credo (anche se avrei ancora problemi nel tradurre correttamente questi frammenti indipendentemente dallo scheduler personalizzato).Alcune interessanti informazioni aggiuntive potrebbe essere quello avuto da
- How to get a Task that uses SynchronizationContext? And how are SynchronizationContext used anyway?
- Await, SynchronizationContext, and Console Apps cui un esempio
SingleThreadSynchronizationContext
è previsto che assomiglia code il lavoro da eseguire. Forse dovrebbe essere usato?
Penso che ho bisogno di menzionare Hopac in questo contesto, come un interessante tangenziale e la menzione anche io sono fuori portata per le prossime 50 ore dispari o giù di lì nel caso in cui tutti i miei messaggi incrociati vanno di mano.
< Edit 3: Daniel e svick dare buoni consigli nei commenti di utilizzare un costruttore un'attività personalizzata. Daniel fornisce un collegamento a uno già definito in FSharpx.
Guardando il la fonte vedo l'interfaccia con i parametri sono definiti come
type TaskBuilder(?continuationOptions, ?scheduler, ?cancellationToken) =
let contOptions = defaultArg continuationOptions TaskContinuationOptions.None
let scheduler = defaultArg scheduler TaskScheduler.Default
let cancellationToken = defaultArg cancellationToken CancellationToken.None
Se si dovesse utilizzare questo a Orleans, sembra che il TaskScheduler
dovrebbe essere TaskScheduler.Current
come da documentazione here
Orleans ha il proprio compito di pianificazione che fornisce il modello filettato esecuzione in un unico utilizzato all'interno di grani. È importante che durante l'esecuzione delle attività venga utilizzato lo scheduler di Orleans e non il pool di thread .NET.
caso il codice di grano richiede un'attività secondaria da creare, si dovrebbe usare Task.Factory.StartNew:
attendono Task.Factory.StartNew (() => {/ * logica * /});
Questa tecnica utilizza l'Utilità di pianificazione in corso, che sarà il Orleans scheduler.
Si dovrebbe evitare di utilizzare Task.Run, che utilizza sempre il filo NET piscina, e quindi non verrà eseguito nel modello singolo thread esecuzione .
E sembra ci sia una sottile differenza tra TaskScheduler.Current e TaskScheduler.Default. Forse questo giustifica una domanda su in quali casi di esempio ci sarà una differenza indesiderata. Come rileva la documentazione Orleans fuori non usare Task.Run
e invece guide per Task.Factory.StartNew
, mi chiedo se si debba definire TaskCreationOptions.DenyAttachChild come è raccomandato da tali autorità come Stephen Toub a Task.Run vs Task.Factory.StartNew e Stephen Cleary a StartNew is Dangerous. Hmm, sembra che il .Default
sarà .DenyAttachChilld
se non mi sbaglio.
Inoltre, in quanto v'è un problema con Task.Run
cioè Task.Factory.CreateNew
per quanto riguarda il programma di pianificazione personalizzata, mi chiedo se questo particolare problema potrebbe essere rimosso utilizzando un personalizzato TaskFactory come spiegato nella Task Scheduler (Task.Factory) and controlling the number of threads e How to: Create a Task Scheduler That Limits Concurrency.
Hmm, questo sta diventando piuttosto una lunga "riflettere" già. Mi chiedo come dovrei chiudere questo?Forse se svick e Daniel potrebbe fare le loro osservazioni, risposte e mi piacerebbe upvote sia e lo accetto svick di?
Non ho usato Orleans, ma se richiede in esecuzione tutto il codice asincrono su una task scheduler personalizzato di codifica in F # sarà un mal di testa da quando 'async' non supporta questo. Il meglio che puoi fare (AFAIK) è convertire ogni 'Task' in' Async <'T> 'con qualcosa come' Async.AwaitTask' (yuck). – Daniel
In effetti, questo è quello che stavo cercando di fare ed evitare i flussi di lavoro asincroni. Ahimè, non sono sicuro se ci sono riuscito e comunque sono un po 'traballante su come tradurre determinati pattern in C# in F # (ad es. Dovrei usare '' async {} '' e se sì, come, o ancora se non lo sono? . Di solito scrivo in entrambe le lingue e non ho avuto la necessità di pensarci davvero. – Veksi
Non conosco F # abbastanza per commentare quella parte, ma volevo solo far notare quelli che credo siano alcuni bug nel codice C# originale dal tuo esempio "here (2)". 'Flush' deve essere un metodo' async Task' piuttosto che 'void' poiché entrambi' HubConnection.Start' e 'IHupProxy.Invoke' sono metodi asincroni che restituiscono' Task' che dovrebbe essere atteso. Probabilmente vuoi raccogliere Attività mentre passi attraverso il ciclo foreach e fai Task.WhenAll alla fine del metodo Flush. –