Sto lavorando molto con i flussi di lavoro asincroni e gli agenti in F #, mentre andavo un po 'più in profondità in Eventi ho notato che il tipo Event < _>() non è Thread- sicuro. Qui non sto parlando del problema comune di creare un evento. In realtà sto parlando di sottoscrizione e rimozione/smaltimento da un evento. Allo scopo di testing ho scritto questo breve programmaUtilizzo dell'evento F # nel codice multi-thread
let event = Event<int>()
let sub = event.Publish
[<EntryPoint>]
let main argv =
let subscribe sub x = async {
let mutable disposables = []
for i=0 to x do
let dis = Observable.subscribe (fun x -> printf "%d" x) sub
disposables <- dis :: disposables
for dis in disposables do
dis.Dispose()
}
Async.RunSynchronously(async{
let! x = Async.StartChild (subscribe sub 1000)
let! y = Async.StartChild (subscribe sub 1000)
do! x
do! y
event.Trigger 1
do! Async.Sleep 2000
})
0
Il programma è semplice, creo un evento e una funzione che sottoscrive una specifica quantità di eventi ad esso, e dopo che smaltire ogni gestore. Io uso un altro calcolo asincrono per generare due istanze di quelle funzioni con Async.StartChild. Al termine di entrambe le funzioni, faccio scattare l'evento per vedere se ci sono ancora alcuni gestori.
Ma quando si chiama event.Trigger(1)
, il risultato è che ci sono ancora alcuni gestori reigsterati all'evento. Poiché alcuni "1" verranno stampati sulla console. Questo in generale significa che la sottoscrizione e/o lo smaltimento non sono thread-safe.
E questo è quello che non mi aspettavo. Se la sottoscrizione e lo smaltimento non sono thread-safe, in che modo è possibile utilizzare gli eventi in generale in modo sicuro? Gli eventi sicuri possono anche essere utilizzati al di fuori dei thread e un trigger non genera alcuna funzione in parallelo o su thread diversi. Ma è in qualche modo normale per me che gli Eventi siano usati in Async, codice basato su Agent o in generale con Threads. Sono spesso usati come una comunicazione per raccogliere informazioni sui thread di Backroundworker. Con Async.AwaitEvent è possibile iscriversi a un evento. Se la sottoscrizione e lo smaltimento non sono thread-safe, come è possibile utilizzare gli eventi in tale ambiente? E quale scopo ha Async.AwaitEvent? Considerando che un flusso di lavoro asincrono fa Thread sperando di utilizzare Async.AwaitEvent è fondamentalmente "broken by design" se la sottoscrizione/eliminazione di un evento non è thread-safe per impostazione predefinita.
La domanda generale che sto affrontando è. È corretto che l'iscrizione e lo smaltimento non siano thread-safe? Dal mio esempio sembra assomigliare, ma probabilmente mi sono perso alcuni dettagli importanti. Attualmente utilizzo molto l'evento nel mio progetto, di solito ho MailboxProcessors e uso eventi per la notifica. Quindi la domanda è Se gli eventi non sono thread-safe, l'intero progetto che sto usando non è assolutamente sicuro per i thread. Allora, qual è una correzione per questa situazione? Creazione di una nuova implementazione di eventi thread-safe? Esistono già alcune implementazioni che affrontano questo problema? O ci sono altre opzioni per utilizzare Event in modo sicuro in un ambiente altamente threaded?
Come per la tua ultima frase. Ogni asincrono è iniziato con Async.Start viene eseguito sul pool di thread, ma anche utilizzando let! o fare! può passare l'asincrono ad un altro thread, per esempio usando semplicemente "Async.Sleep" si passa al pool di thread o usando Async.StartChild. In cima sto facendo scattare l'evento da MailboxProcessor e anche quelli che eseguono sempre alcuni thread nel pool di thread. Quindi non vedo come far funzionare tutto su un thread singolo. Ma anche io non vorrei questo comportamento. Mi aspetto questo tipo di comportamento in quanto non voglio comunque eseguire tutto su un thread singolo. –
Sembra che la mancanza di sicurezza del thread sugli eventi sia un problema significativo con la libreria di base F #. Prenderesti in considerazione l'apertura di un problema per questo? –
Puoi arrotolare le tue proprie coroutine o assicurarti che ogni 'let!' 'Do!' Rimandi al "thread principale". Ma come hai detto tu non vuoi comunque questo comportamento, quindi non importa. – FuleSnabel