问题
I'm trying to implement a multithreading refresh UI in my Vaadin app.
Part of this UI is a dChart
based on container. To build dChart
Im iterating through container and count elements by one of their properties, like this:
Collection<?> itemIDS = container.getItemIds();
for (Object itemID : itemIDS) {
Property property = container.getContainerProperty(itemID, "status");
String status = (String) property.getValue();
if (countMap.containsKey(status)) {
countMap.put(status, countMap.get(status) + 1);
} else {
countMap.put(status, 1);
}
}
However it takes over 2-3 seconds if container has thousands of elements.
User just can't wait so long to refresh an UI.
I read that i can build my UI and later just refresh it using @Push
Vaadin annotation, after dChart is fully built.
So i build something like this:
{
//class with @Push
void refreshPieDChart(GeneratedPropertyContainer container) {
new InitializerThread(ui, container).start();
}
class InitializerThread extends Thread {
private LogsUI parentUI;
private GeneratedPropertyContainer container;
InitializerThread(LogsUI ui, GeneratedPropertyContainer container) {
parentUI = ui;
this.container = container;
}
@Override
public void run() {
//building dChart etc etc... which takes over 2-3 seconds
// Init done, update the UI after doing locking
parentUI.access(new Runnable() {
@Override
public void run() {
chart.setDataSeries(dataSeries).show();
}
});
}
}
}
However if i refresh page few times, it is generating errors about SQLContainer
:
java.lang.IllegalStateException: A trasaction is already active!
Becouse after few refreshes, my multiple threads are running parallel using the same SQLContainer.
To fix that, I want to stop all working refresh threads except last one to eliminate concurrent problem. How i can do it? Maybe other solution?
EDIT: I have tried smth like this, but problem still remains, is it a correct way to prevent concurrent problem?
{
private static final Object mutex = new Object();
//class with @Push
void refreshPieDChart(GeneratedPropertyContainer container) {
new InitializerThread(ui, container).start();
}
class InitializerThread extends Thread {
private LogsUI parentUI;
private GeneratedPropertyContainer container;
InitializerThread(LogsUI ui, GeneratedPropertyContainer container) {
parentUI = ui;
this.container = container;
}
@Override
public void run() {
//is it correct way to prevent concurrent problem?
synchronized(mutex){
//method to refresh/build chart which takes 2-3s.
}
// Init done, update the UI after doing locking
parentUI.access(new Runnable() {
@Override
public void run() {
chart.setDataSeries(dataSeries).show();
}
});
}
}
}
回答1:
This is what i did:
Henri Kerola points me pretty obvious idea: do counting in SQL. As i said I was thinking about that but that would means I need to prepare SQL for every possible filters combination. That is a pretty complicated and doesn't look good.
But this realised me that if I'm using SQLContainer with filters for my table, I can do the same for counting. I just need to create second SQLContainer
with my ownFreeformQuery
and FreeformStatementDelegate
.
If i will create all of above, i can just add THE SAME filters to both containers however i dont need to count elements now becouse 2nd container holds values for me. It sounds complecated but take a look on my code:
FreeformQuery myQuery = new MyFreeformQuery(pool);
FreeformQuery countQuery = new CountMyFreeformQuery(pool);
SQLContainer myContainer = new SQLContainer(myQuery); //here i hold my all records as in a Question
SQLContainer countContainer = new SQLContainer(countQuery); //here i hold computed count(*) and status
MyFreeformQuery.java looks like:
class ProcessFreeformQuery extends FreeformQuery {
private static final String QUERY_STRING = "SELECT request_date, status, id_foo FROM foo";
private static final String COUNT_STRING = "SELECT COUNT(*) FROM foo";
private static final String PK_COLUMN_NAME = "id_foo";
MyFreeformQuery(JDBCConnectionPool connectionPool) {
super(QUERY_STRING, connectionPool, PK_COLUMN_NAME);
setDelegate(new AbstractFreeformStatementDelegate() {
protected String getPkColumnName() {
return PK_COLUMN_NAME;
}
protected String getQueryString() {
return QUERY_STRING;
}
protected String getCountString() {
return COUNT_STRING;
}
});
}
and most important CountFreeformQuery.java looks like:
public class CountFreeformQuery extends FreeformQuery {
private static final String QUERY_STRING_GROUP = "SELECT status, count(*) as num FROM foo GROUP BY status";
private static final String QUERY_STRING = "SELECT status, count(*) as num FROM foo";
private static final String GROUP_BY = "foo.status";
public CountFreeformQuery(JDBCConnectionPool connectionPool) {
super(QUERY_STRING_GROUP, connectionPool);
setDelegate(new CountAbstractFreeformStatementDelegate() {
protected String getQueryString() {
return QUERY_STRING;
}
protected String getGroupBy(){
return GROUP_BY;
}
});
}
}
Now if i want to refresh dChart
after smth like that:
myContainer.addContainerFilter(new Between(DATE_PROPERTY, getTimestamp(currentDate), getOneDayAfter(currentDate)));
I just do the same with countContainer
:
countContainer.addContainerFilter(new Between(DATE_PROPERTY, getTimestamp(currentDate), getOneDayAfter(currentDate)));
And pass it to method which dont need to count elements, just add all container to map and then to dChart like that:
Map<String, Long> countMap = new HashMap<String, Long>();
Collection<?> itemIDS = container.getItemIds();
for (Object itemID : itemIDS) {
Property statusProperty = container.getContainerProperty(itemID, "status");
Property numProperty = container.getContainerProperty(itemID, "num");
countMap.put((String) statusProperty.getValue(), (Long) numProperty.getValue());
}
Now i have status
es of elements in myContainer
counted, no need to multithreading or writing tons of sql.
Thanks guys for suggestions.
来源:https://stackoverflow.com/questions/31423764/multithreading-refresh-ui-in-vaadin