Vaadin 中的多线程刷新 UI
Multithreading refresh UI in Vaadin
我正在尝试在我的 Vaadin 应用程序中实现多线程刷新 UI。
此 UI 的一部分是基于容器的 dChart
。要构建 dChart
我将遍历容器并按其属性之一对元素进行计数,如下所示:
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);
}
}
但是,如果容器有数千个元素,则需要 2-3 秒。
用户等不及刷新 UI。
我读到我可以构建我的 UI,然后在完全构建 dChart 之后使用 @Push
Vaadin 注释刷新它。
所以我构建了这样的东西:
{
//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();
}
});
}
}
}
但是,如果我刷新页面几次,它会生成关于 SQLContainer
:
的错误
java.lang.IllegalStateException: A trasaction is already active!
因为在几次刷新之后,我的多个线程使用同一个 SQLContainer 运行 并行。
为了解决这个问题,我想停止除最后一个以外的所有工作刷新线程以消除并发问题。我该怎么做?也许其他解决方案?
编辑:
这样试过,还是有问题,请问是不是防止并发问题的正确方法?
{
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();
}
});
}
}
}
这就是我所做的:
Henri Kerola 指出了我非常明显的想法:在 SQL 中进行计数。正如我所说,我正在考虑这个问题,但这意味着我需要为每个可能的过滤器组合准备 SQL。这是一个非常复杂的,看起来不太好。
但这让我意识到,如果我为我的 table 使用带有过滤器的 SQLContainer,我可以做同样的事情来进行计数。我只需要用我自己的 FreeformQuery
和 FreeformStatementDelegate
创建第二个 SQLContainer
。
如果我将创建以上所有内容,我可以只向两个容器添加相同的过滤器,但是我现在不需要计算元素,因为第二个容器为我保存值。听起来很复杂,但看看我的代码:
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 看起来像:
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;
}
});
}
最重要的 CountFreeformQuery.java 看起来像:
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;
}
});
}
}
现在,如果我想在这样的事情之后刷新 dChart
:
myContainer.addContainerFilter(new Between(DATE_PROPERTY, getTimestamp(currentDate), getOneDayAfter(currentDate)));
我只是对 countContainer
做同样的事情:
countContainer.addContainerFilter(new Between(DATE_PROPERTY, getTimestamp(currentDate), getOneDayAfter(currentDate)));
并将其传递给不需要计算元素的方法,只需将所有容器添加到映射,然后像这样添加到 dChart:
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());
}
现在我在 myContainer
中计算了 status
es 个元素,不需要多线程或编写大量 sql。
感谢大家的建议。
我正在尝试在我的 Vaadin 应用程序中实现多线程刷新 UI。
此 UI 的一部分是基于容器的 dChart
。要构建 dChart
我将遍历容器并按其属性之一对元素进行计数,如下所示:
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);
}
}
但是,如果容器有数千个元素,则需要 2-3 秒。
用户等不及刷新 UI。
我读到我可以构建我的 UI,然后在完全构建 dChart 之后使用 @Push
Vaadin 注释刷新它。
所以我构建了这样的东西:
{
//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();
}
});
}
}
}
但是,如果我刷新页面几次,它会生成关于 SQLContainer
:
java.lang.IllegalStateException: A trasaction is already active!
因为在几次刷新之后,我的多个线程使用同一个 SQLContainer 运行 并行。
为了解决这个问题,我想停止除最后一个以外的所有工作刷新线程以消除并发问题。我该怎么做?也许其他解决方案?
编辑: 这样试过,还是有问题,请问是不是防止并发问题的正确方法?
{
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();
}
});
}
}
}
这就是我所做的:
Henri Kerola 指出了我非常明显的想法:在 SQL 中进行计数。正如我所说,我正在考虑这个问题,但这意味着我需要为每个可能的过滤器组合准备 SQL。这是一个非常复杂的,看起来不太好。
但这让我意识到,如果我为我的 table 使用带有过滤器的 SQLContainer,我可以做同样的事情来进行计数。我只需要用我自己的 FreeformQuery
和 FreeformStatementDelegate
创建第二个 SQLContainer
。
如果我将创建以上所有内容,我可以只向两个容器添加相同的过滤器,但是我现在不需要计算元素,因为第二个容器为我保存值。听起来很复杂,但看看我的代码:
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 看起来像:
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;
}
});
}
最重要的 CountFreeformQuery.java 看起来像:
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;
}
});
}
}
现在,如果我想在这样的事情之后刷新 dChart
:
myContainer.addContainerFilter(new Between(DATE_PROPERTY, getTimestamp(currentDate), getOneDayAfter(currentDate)));
我只是对 countContainer
做同样的事情:
countContainer.addContainerFilter(new Between(DATE_PROPERTY, getTimestamp(currentDate), getOneDayAfter(currentDate)));
并将其传递给不需要计算元素的方法,只需将所有容器添加到映射,然后像这样添加到 dChart:
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());
}
现在我在 myContainer
中计算了 status
es 个元素,不需要多线程或编写大量 sql。
感谢大家的建议。