2014-07-17 11 views
15

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

  1. AddHub ha una firma del private async Task AddHub(string address).
  2. RefreshHubs chiama AddHub in un ciclo e raccoglie un elenco di tasks, che attende poi alla fine da await Task.WhenAll(tasks) e di conseguenza il valore di ritorno corrisponde la sua firma di private async Task RefreshHubs(object _).
  3. RefreshHubs viene chiamato da ActivateAsync come await RefreshHubs(null) e quindi alla fine c'è una chiamata await base.ActivateAsync() corrispondente alla firma della funzione public 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

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?

+2

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

+0

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

+0

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. –

risposta

1

È possibile utilizzare lo TaskBuilder in FSharpx e passare TaskScheduler.Current. Ecco il mio tentativo di tradurre RefreshHubs. Si noti che Task<unit> viene utilizzato al posto di Task.

let RefreshHubs _ = 
    let task = TaskBuilder(scheduler = TaskScheduler.Current) 
    task { 
     let addresses = 
      RoleEnvironment.Roles.["GPSTracker.Web"].Instances 
      |> Seq.map (fun instance -> 
       let endpoint = instance.InstanceEndpoints.["InternalSignalR"] 
       sprintf "http://%O" endpoint.IPEndpoint 
      ) 
      |> Seq.toList 

     let newHubs = addresses |> List.filter (not << hubs.ContainsKey) 
     let deadHubs = hubs.Keys |> Seq.filter (fun x -> 
      not (List.exists ((=) x) addresses)) 

     // remove dead hubs 
     deadHubs |> Seq.iter (hubs.Remove >> ignore) 

     // add new hubs 
     let! _ = Task.WhenAll [| for hub in newHubs -> AddHub hub |] 
     return() 
    } 
+0

Questo è un buon tentativo. :) Penso di averne l'essenza (e il codice C# era un po '"oh no" con tutto il materiale "void FunctionMutatesInternalState()". Anche in C# e classi è più facile passare i parametri e raccogliere i valori di ritorno e assegnali da qualche parte Oh bene, questa è stata una buona corsa. Grazie! – Veksi