Conversation
|
@h0lg Thank you very much for your PR. It's looking very good. Would you have an example where |
Sure, please have a closer look at above example. It uses I've tried to describe this here: Feel free to suggest a better name for the concept. Maybe I found this visualization helpful to grasp the difference between throttle and debounce: https://web.archive.org/web/20220117092326/http://demo.nimius.net/debounce_throttle/ |
|
I've added a type Msg = | Batch of int list // enables batching values of the same type for dispatch
// a factory that batches and dispatches the pending values every 100 ms; takes a value and produces a Cmd
// declare per program or long running background task
let createThrottledMsgBatchCmd = batchedThrottle 100 (fun values ->
System.Diagnostics.Debug.WriteLine("dispatching a batch of values to the MVU loop " + values.ToString())
Batch values)
// an optional wrapper for usage inside of Cmd.ofEffect giving the factory function a dispatch signature
let dispatchToThrottledFactory value =
System.Diagnostics.Debug.WriteLine("dispatching a single value to the throttled batch factory " + value.ToString())
createThrottledMsgBatchCmd value |> List.iter (fun effect -> effect dispatch) // make the MVU dispatch available to the returned command
produceIntegersFast dispatchToThrottledFactory // prevents this function from spamming the MVU loop |
|
I've added a second return value to let createCmd, awaitNextDispatch = Cmd.batchedThrottle 100 NewValues
... some awaited (!) producers using createCmd ...
// wait until next dispatch plus a little optional buffer to avoid race conditions
let! _ = awaitNextDispatch (Some(TimeSpan.FromMilliseconds(10)))
// I can be sure now all messages have been dispatched What do you think about this pattern? |
473db05 to
8108219
Compare
|
In the latest iteration I've rewritten This version, I can for example write extensions like type AsyncEnumerableExtensions =
[<Extension>]
static member dispatchTo((this: System.Collections.Generic.IAsyncEnumerable<'result>), (dispatch: 'result -> unit)) =
async {
let results = this.GetAsyncEnumerator()
let rec dispatchResults () =
async {
let! hasNext = results.MoveNextAsync().AsTask() |> Async.AwaitTask
if hasNext then
results.Current |> dispatch
do! dispatchResults ()
}
do! dispatchResults ()
}
[<Extension>]
static member dispatchBatchThrottledTo
(
(this: System.Collections.Generic.IAsyncEnumerable<'result>),
throttleInterval,
(mapPendingResultsToBatchMsg: 'result list -> 'msg),
(dispatch: 'msg -> unit)
) =
async {
// create a throttled dispatch of a batch of pending results at regular intervals
let dispatchSingleResult, awaitNextDispatch =
dispatch.batchThrottled (throttleInterval, mapPendingResultsToBatchMsg)
do! this.dispatchTo dispatchSingleResult // dispatch single results using throttled method
do! awaitNextDispatch (Some throttleInterval) // to make sure all results are dispatched before calling it done
}and then throttle the progress reporting as well as the result dispatch effectively: type Msg
| SearchProgressReports of BatchProgress list
| SearchResults of SearchResult list
| SearchCompleted
let private searchCmd model =
fun dispatch ->
async {
let command = mapToSearchCommand model
let dispatchProgress, awaitNextProgressDispatch = dispatch.batchThrottled(100, SearchProgressReports)
let reporter = Progress<BatchProgress>(dispatchProgress)
use cts = new CancellationTokenSource()
do! searchAsync(command, reporter, cts.Token).dispatchBatchThrottledTo (100, SearchResults, dispatch)
do! awaitNextProgressDispatch (Some 100) // to make sure all progresses are dispatched before calling it done
dispatch SearchCompleted
} |> Async.StartImmediate
|> Cmd.ofEffectWhether - and if, in what form - you want this in Fabulous is up to you. But I found it this helpful to prevent the MVU loop from choking up when feeding too many messages into it too rapidly. |
to prevent misuse
… command factories similar to Cmd.debounce
…o await the next dispatch Should debounce and bufferedThrottle follow the same API?
because it feels more natural to use it that way with a dispatch inside an ofEffect that produces values rapidly
da1f926 to
63f739a
Compare
The throttling methods are intended for stuff like throttling
Progress<>updates from background tasks like the following search does while yielding results from anIAsyncEnumerable:I'm a Fabulous and F# freshie, so please have a good hard look at my changes and above intended use case. Feel free to point out anything that looks weird or cumbersome to you - I'm here to learn :)