Multithreading refresh UI in Vaadin

混江龙づ霸主 提交于 2020-01-25 01:24:08

问题


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 statuses 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!