Platform.runLater - java

I have and app that connects to a socket connection and that connections sends me a lot of info.. lets say 300 orders per second (maybe more).. I have a class (it is like a listener, that reacts to some event and that event has the order) that receives that order.. creates an object and then adds it to an ObservableList (which is the source of a tableView).. that way my GUI shows that order. But here comes the problem, if that order already exists on the observableList.. i can't add it ..and i must update it (wich i do).. but some times.. with some orders this condition doesn't work and the order its added again.
Im gonna show you how it's work with some code.
public class ReceivedOrderListener
{
ev = Event; //Supose that this is the event with the order
if(!Repository.ordersIdMap.containsKey(ev.orderID))
{
Platform.runLater(new Runnable()
{
#Override public void run()
{
Repository.ordersCollection.add(ev.orderVo);
}
}
});
Repository.ordersIdMap.put(ev.orderID, ev.orderVo);
}
Ok now.. this is a resume of my code. The ev is my event with all the info of the order, the orderID is the key that i use to see if the order already exists or not (and yeah is unique). The "Repository" is a singleton class, the "ordersCollection" is a ObservableList, the "ordersIdMap" is a HashMap

If ReceivedOrderListener is executed by multiple threads, then it looks like "check-then-act" race condition.
-> ORDER1 comes to the listener
T1 checks ordersIdMap.containsKey(ORDER1) it returs false
T1 proceeds to do Platform.runLater to add the order
-> ORDER1 comes to the listener again
-> T2 checks ordersIdMap.containsKey(ORDER1) it returs false again
now T1 proceeds to do ordersIdMap.put(ORDER1)
-> T2 proceeds to do Platform.runLater to add the order again

Related

JavaFX task/thread not filling tableview consistently

So, i need to fill a Table View using a JavaFX thread but the table is being filled only ~70% of time. I am looking at my code and i really can't find where the problem comes from, my guess is that the task is somehow being executed before the data is successfully retrieved/processed from db. Thanks is advance :)
private Executor exec;
private ObservableList<User> cellData = FXCollections.observableArrayList();
.
.
.
public void fillTable(HashMap<String,Object> whereClause){
Task<List<User>> task = new Task<List<User>>(){
#Override
public ObservableList<User> call(){
cellData.clear();
cellData.addAll(userRepository.getAll(whereClause));
userId.setCellValueFactory(new PropertyValueFactory<>("userID"));
userName.setCellValueFactory(new PropertyValueFactory<>("userName"));
userMail.setCellValueFactory(new PropertyValueFactory<>("userMail"));
userPhone.setCellValueFactory(new PropertyValueFactory<>("userPhone"));
isAdmin.setCellValueFactory(cellData -> {
String isAdminAsString = cellData.getValue().isAdmin() ? "Admin" : "Medic";
return new ReadOnlyStringWrapper(isAdminAsString);
});
isDeleted.setCellValueFactory(cellData -> {
String isActiveUser = cellData.getValue().isDeleted() ? "No" : "Yes";
return new ReadOnlyStringWrapper(isActiveUser);
});
logger.info("Cell values set");
return cellData;
}
};
exec.execute(task);
task.setOnFailed(e -> System.out.println(task.getException().getMessage()));
task.setOnSucceeded(e -> userTable.setItems((ObservableList<User>) task.getValue()));
logger.info("Fill user Table Task executed");
You don't give enough context for a proper, fully confident answer, but my guess is you're encountering issues relating to threads. JavaFX is not thread-safe; using the wrong thread to update the UI can lead to undefined behavior, such as the data only appearing ~70% of the time. There's an important rule in JavaFX that you must always follow:
Never read or write the state of objects that are connected—directly or indirectly—to a live scene graph on a thread other than the JavaFX Application Thread.
Your code does not follow this rule. Inside the call method of your Task you are structurally modifying cellData and setting the cellValueFactory of various TableColumns. This leads to said objects being modified by whatever thread is executing the Task. If the Executor is any hint, that thread is definitely not the JavaFX Application Thread.
I'm not sure why you're setting the cellValueFactory of your TableColumns inside the call method in the first place. The cell value factory is configuration that only needs to be done once—when you create the TableColumn (or shortly thereafter). In other words, configuring the cell value factory in the call method is wrong not just because it happens on a background thread but also because it happens each time you execute the Task. Remove the set-the-cell-value-factory code from the call method and move it, if needed, to where you're creating the TableColumns. If you're using FXML, and the TableColumns are created for you and injected, then the controller's initialize method is a good place for this sort of configuration.
Your cellData list is connected to your TableView, if not at first then definitely after the first successful execution of your Task. Modifying cellData on a background thread will notify the TableView of those changes on the same thread (listeners are invoked on the same thread that made the change). The easy solution is to have your Task return a new List and then update the TableView if successful.
Task<List<User>> task = new Task<List<User>>() {
#Override protected List<User> call() throws Exception {
return userRepository.getAll(whereClause);
}
});
task.setOnSucceeded(event -> userTable.getItems().setAll(task.getValue()));
task.setOnFailed(event -> task.getException().printStackTrace());
exec.execute(task);
The setAll method of ObservableList will first clear the list then add all the elements of the given collection (or array). This is somewhat more efficient than calling clear followed by addAll because it results in only one change event. Also, if you want to continue using cellData you can, assuming you've previously set it as your table's items; just use cellData.setAll(task.getValue()) instead.
Regarding the use of:
task.setOnSucceeded(e -> userTable.setItems((ObservableList<User>) task.getValue()));
Since you clearly expect an ObservableList<User> to be returned, you should be using a Task<ObservableList<User>> instead of a Task<List<User>>. This will mean getValue() returns ObservableList<User> and thus the cast becomes unneeded. However, if you follow the advice above, then this is irrelevant.

invokeLater strange call, after other methods

I am developing a library program and when I make a given successive actions, I get an unexpected row added to my books' JTable.
The actions are this:
launching of the program
add a filter based on the commentaries (I have many filters, which are classes inheriting from RowFilter, they are all associated to the RowSorter of the JTable. This part of the program works fine).
In the initial state, there are 2 book in the JTable, which are "le livre de la jungle" and "Eloge des mathématiques". After the use of the filter, there only remains one, "Eloge des mathématiques". The other is not displayed but is still in the model.
select in the JTable the remaining book("le livre de la jungle"). With the debugger, I saw that during the actions triggered by this click, a book with an empty title and author is created. It is not visible but if I
deactivate all the filters, There are now 3 books in the JTable. (2 previous one plus the "ghost" with dummy values.) The dummy values come from a method, saveChanges(), which saves the current fields.
Here is a picture of the program, with the ribbon(north), the fields(center), and the JTable(bottom).
you can see the additional row, unexpected.
Now, let's see some code:
When I click on the JTable's row, I trigger this method:
table.getSelectionModel().addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) {
if (table.getSelectedRow() > -1) {
Book book = ((TableATM) table.getModel()).getBook(table
.convertRowIndexToModel(table.getSelectedRow()));
getStatesManager().getState().selectBook(book);
}
}
});
This calls selectBook, from the UnboundState class. There are 2 states for the window: BoundState & UnboundState. UnboundState is used when the fields are empty. When the fields contain the datas of a book, (e.g. after a remote search on internet with the ISBN) it is immediately saved and the state becomes bound.
After the search I talked about above, the state remains the same as the initial one, I mean UnboundState : the search only changes the JTable, not the fields.
so, UnboundState's selectBook is triggered :
#Override
public void selectBook(Book book) {
statesManager.setBound(book);
}
the setBound method's purpose is to change the state :
public void setBound(Book book) {
state = new BoundState(book, this, bookWindow,
new ChoiceISBNDialog(bookWindow));
}
here is the constructor called by setBound:
public BoundState(Book book, StatesManager statesManager,
BookWindow bookWindow, ChoiceISBNDialog choice) {
this.statesManager = statesManager;
this.bookWindow = bookWindow;
this.choice = choice;
SwingUtilities.invokeLater(() -> {
statesManager.enableFields(true);
if (statesManager.getState().getName().equals("BOUND"))
saveChanges();
statesManager.displayBook(book);
statesManager.setCaretsToZero();
bookWindow.ribbon.btn_revertChanges.setEnabled(false);
bookWindow.ribbon.btn_storeInTable.setEnabled(false);
});
}
The important thing is that th emethod saveChanges() should not be called, because the current state when the invokeLater is called is "UNBOUND".
But when I debug the program, I get this: the methods are ran, following the odrer I gave to you, except that the debugger don't go into the invokeLater, it continues, and the methods finish, the last one is the JTable listener. but I put a breakpoint in the saveChanges() method, and I see that the saveChanges() method is triggered AFTER the end of the JTable's listener. This is an effect of the invokeLater I presume. But at the time, the state became "BOUND" and the saveChanges method think that the book actually in the fields is new and tries to save it.
How could I make it work? I tried a call to invokeAndWait but it freezes the program (it is definitely stopped, even after 1 minute or more).

RxJava: How to prepend startWith() default emit EVERY TIME parent observable emits?

im trying to have a pattern, where my observable which produces some object, is transformed into domain events like Started, Success, Error emited around the observable producing, if that makes sense
public Observable<BookRenderingEvent> extractAndRenderObservable(String epubPath) {
return extractObservable(epubPath)
.flatMapObservable(extractedEpub -> renderObservable(extractedEpub)
.<BookRenderingEvent>map(renderedEpub -> new BookRenderingEvent.Success(renderedEpub))
.onErrorReturn(t -> new BookRenderingEvent.Error())
.startWith(new BookRenderingEvent.Started()));
}
private Observable<RenderedEpub> renderObservable(ExtractedEpub extractedEpub) {
return Observable.combineLatest(readerConfigObservable(), pagerDimensionsObservable(), ..)
.switchMapSingle(foo -> doRenderObservable()) <--- heavy work
.map(bar -> new RenderedEpub(bar))
}
renderObservable contains a heavy action so I want to emit these state events, so UI can react accordingly (with success containing the extractedEpub object as you can see in the map)
What my problem is that, renderObservable contains combineLatest(), so it "stays open" and emit mutiple times in time, whenever its obervables emit.
So the flow of events is Started, Success, Succes ... Success.
I want it to be Started, Success, Started, Success .. etc. i.e prepend Started event whever combineLatest emits, but my rx knowledge is insufficient.
Thanks
You could insert the following into the observable chain at the right place:
.flatMap( event -> Observable.just( new BookRenderingEvent.Started(), event )
This will emit the Started event before every event that it receives.
Of course, you could add in some logic so that you don't issue Started if the event is Started, etc.
Ok Ive managed to figure it out. The key info I was missing is that right side of flatmap gets subscribed when left side emits. Therefore the startWith had to be moved to the right side of flatmap observable, that gets subscribed to when ever combineLatest emits
public Observable<BookRenderingEvent> extractAndRenderObservable(String epubPath) {
return extractObservable(epubPath)
.flatMap(extractedEpub -> Observable.combineLatest(readerConfigObservable(), pagerDimensionsObservable(), ..)
.switchMap(foo -> renderObservable(extractedEpub)
.<BookRenderingEvent>map(renderedEpub -> new BookRenderingEvent.Success(renderedEpub))
.onErrorReturn(t -> new BookRenderingEvent.Error())
.startWith(new BookRenderingEvent.Started()));
}

Store last subscriptions of consumers in hashmap

I have Rx Stream where I'm using a grouped observable to have multiple observable according to a discriminant value. The thing is that I want to keep in memory the last subscription of each group observable in order to delete the stream when a new one is created (unsubscribe)
I'm using a simple Hashmap to store value according to a key which is an observable it self but it doesn't to worth it.
Here is where I am so far:
stream
.groupBy(ts -> ts.getLogin())
.map(receptor -> receptor.asObservable())
.forEach(consumer -> {
controlConsumer(consumer);
});
public void controlConsumer(Observable<Number> pConsumer) {
Subscription mySubscription = pConsumer
.buffer(2,1)
.subscribe();
// Remove the current consumer last subscription if existing
if(consumersLastSubcriptions.containsKey(pConsumer)) {
LOG.info("removing last subscription");
Subscription lLastSubscription = consumersLastSubcriptions.get(pConsumer);
lLastSubscription.unsubscribe();
}
// store the new consumer subscription
consumersLastSubcriptions.put(pConsumer, lSubscription);
}
UPDATE
Actually i might have different items defined by the login of the user emitting them. What i want is to store the last subscription of each user in order to remove it when i need to recreate a new one when needed. My purpose is to shutdown every stream no more used and don't let them open for nothing.

a Service, a Thread, an Activity and a static ArrayList

I have a non-sticky service that's called on a regular basis via a broadcastreceiver to start a thread that'll perform some tasks. While the thread is running an ongoing notification shows some progress information, and a button to bring up a status page.
This status page shows a lists of items curerntly being processed, this list is a static ArrayList used by both the thread and this activity. When the status Activity is started I have a null check:
if(Global.statusItems == null)
{
Global.statusItems = new ArrayList<StatusPageItem>();
}
The thread is still running, and has perfectly fine access to the ArrayList, but as soon as the Status Activity is brought up it'll recreate the ArrayList as if it were null.
So far I haven't been able solve the issue without saving the list using an ObjectOutputStream and reloading when the status page is started. Is there a more elegant solution I could use?
Regards,
Quint.
Is it possible that your service is running on a different process?
You need to make sure that the 2 lines of code (null test and creation of a new list) are atomic and that the allocation is visible from other threads.
The easiest way to do that is to synchronize that piece of code:
synchronized(Global.class) {
if(Global.statusItems == null) {
Global.statusItems = new ArrayList<StatusPageItem>();
}
}
However, if you need to read the list from one thread and write to it from another thread, you will need to add extra synchronization when adding/removing/iterating to make sure that both treads see the same list - if you don't, it is possible that the writing thread adds an item to the list but the reading thread does not see it.
The easiest way would be to use a thread safe implementation of list:
synchronized(Global.class) {
if(Global.statusItems == null) {
Global.statusItems = new CopyOnWriteArrayList<StatusPageItem>();
}
}
If memory / object creation is a concern (CopyOnWriteArrayList is not very efficient from that perspective), you can also use a synchronized collection instead:
synchronized(Global.class) {
if(Global.statusItems == null) {
Global.statusItems = Collections.synchronizedList(new ArrayList<StatusPageItem>());
}
}
In that case, make sure you lock on the collection when iterating:
synchronized(Global.statusItems) {
for (StatusPageItem item : Global.statusItems) {
}
}

Categories