Infinite loop when messages are sent too quickly in succession
See original GitHub issueUpdated with workaround
Applies to: Android (maybe iOS)
Description:
To update the model along with what the user does, I listen to events like TextChanged
.
This works fine.
But under some circumstances, such event might trigger an infinite update-view loop which will freeze the app.
Based on what I do and the logs, I suspect EXF doesn’t like TextChanged to trigger too quickly in a row.
I will update this issue when I find more about this.
iOS seems not to be affected.
Logs In ElmishContacts, I searched for a contact “Hoya Booya” and then erased my search.
Message: MainPageMsg (TabAllContactsMsg (UpdateFilterText "Ho"))
Updated model: ...
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Label
View, model = ...
View result: NavigationPage(...)@821839065
Updating NavigationPage, prevCount = 1, newCount = 1
Adjust page number 0
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Image
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Label
Create Xamarin.Forms.Label
Create Xamarin.Forms.Image
Message: MainPageMsg (TabAllContactsMsg (UpdateFilterText "H"))
Updated model: ...
View, model = ...
View result: NavigationPage(...)@571913183
Updating NavigationPage, prevCount = 1, newCount = 1
Adjust page number 0
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Label
Message: MainPageMsg (TabAllContactsMsg (UpdateFilterText ""))
Updated model: ...
View, model = ...
View result: NavigationPage(...)@936190227
Updating NavigationPage, prevCount = 1, newCount = 1
Adjust page number 0
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Label
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Image
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Label
Create Xamarin.Forms.Label
[IInputConnectionWrapper] beginBatchEdit on inactive InputConnection
[IInputConnectionWrapper] getTextBeforeCursor on inactive InputConnection
Create Xamarin.Forms.Image
Message: MainPageMsg (TabAllContactsMsg (UpdateFilterText "H"))
Updated model: ...
View, model = ...
View result: NavigationPage(...)@906179063
Updating NavigationPage, prevCount = 1, newCount = 1
Adjust page number 0
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Label
Message: MainPageMsg (TabAllContactsMsg (UpdateFilterText ""))
Updated model: ...
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Label
View, model = ...
View result: NavigationPage(...)@765267125
Updating NavigationPage, prevCount = 1, newCount = 1
Adjust page number 0
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Image
[IInputConnectionWrapper] getTextAfterCursor on inactive InputConnection
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Label
Create Xamarin.Forms.Label
Create Xamarin.Forms.Image
Message: MainPageMsg (TabAllContactsMsg (UpdateFilterText "H"))
Updated model: ...
View, model = ...
View result: NavigationPage(...)@991281053
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Label
Updating NavigationPage, prevCount = 1, newCount = 1
Adjust page number 0
Message: MainPageMsg (TabAllContactsMsg (UpdateFilterText ""))
Updated model: ...
View, model = ...
View result: NavigationPage(...)@52470701
Updating NavigationPage, prevCount = 1, newCount = 1
Adjust page number 0
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Label
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Image
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Label
Create Xamarin.Forms.Label
[IInputConnectionWrapper] getSelectedText on inactive InputConnection
Create Xamarin.Forms.Image
Message: MainPageMsg (TabAllContactsMsg (UpdateFilterText "H"))
Updated model: ...
View, model = ...
View result: NavigationPage(...)@594339090
Updating NavigationPage, prevCount = 1, newCount = 1
Adjust page number 0
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Label
Message: MainPageMsg (TabAllContactsMsg (UpdateFilterText ""))
Updated model: ...
View, model = ...
View result: NavigationPage(...)@1068286510
Updating NavigationPage, prevCount = 1, newCount = 1
Adjust page number 0
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Label
[IInputConnectionWrapper] endBatchEdit on inactive InputConnection
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Image
Create Xamarin.Forms.StackLayout
Create Xamarin.Forms.Label
Create Xamarin.Forms.Label
Create Xamarin.Forms.Image
[IInputConnectionWrapper] beginBatchEdit on inactive InputConnection
[IInputConnectionWrapper] getTextBeforeCursor on inactive InputConnection
// And on, and on...
Repro: https://github.com/TimLariviere/EXF_ReproInfiniteLoop
- Start the repro
- Type some text slowly in the Entry field (like you would normally)
- Working!
- Start mashing your keyboard (write quickly) or erase the field
- Not working. EXF goes berserk.
Workaround:
The issue only occurs when the UI updates quicker than EXF follows, so one way to prevent that is to throttle the TextChanged
event and only send a single message when no more TextChanged
are triggered for a given time.
Here’s an example with a 250ms timeout.
One important point: we need to “fix” the throttle function with fixf
=> fixf throttle dispatch 250
.
The throttle func start a new loop each time it is called, and without caching it with fixf
, a new loop would be created each time the view refreshes.
let throttle fn timeout =
let mailbox = MailboxProcessor.Start(fun agent ->
let rec loop lastMsg = async {
let! r = agent.TryReceive(timeout)
match r with
| Some msg ->
return! loop (Some msg)
| None when lastMsg.IsSome ->
fn lastMsg.Value
return! loop None
| None ->
return! loop None
}
loop None
)
mailbox.Post
let view (model: Model) dispatch =
let throttledDispatch = fixf throttle dispatch 250
View.ContentPage(
content = View.StackLayout(
children = [
View.Entry(
text=model.TextWorkaround,
textChanged=(fun e -> throttledDispatch (TextChangedWorkaround e.NewTextValue)))
]))
Issue Analytics
- State:
- Created 5 years ago
- Comments:15 (8 by maintainers)
Top GitHub Comments
Ah yes indeed. I think the easiest way to solve this would be to extract the condition from the event handler. Losing focus won’t happen as often as TextChanged, so you should be fine dispatching NameFocusLost everytime.
@willsam100 since it is now possible to capture the control we can do this:
Are there any downsides to this, apart from having to access the underlying control? Is it better not to compare the model and control values?
The
Unfocused
event has been around since at least XF3.1. Is there a particular reason it is not in Fabulous?