0

Why do long-running tasks block the UI?

asked 2011-07-01 09:35:29 +0800

valmar gravatar image valmar
925 2 13
http://www.timo-ernst.net

I know that it's recommended to handle long-running tasks in a separate thread on server-side.

But I just wondered:

If such a long-running task is running on the server, why does it block the UI (e.g. button click events wait until the long running task has finished).

I am asking because, as far as I know, every (ajax) request to the server should be handled in a new thread automatically, or not?
I mean, it's not like there is one big thread that takes care about all requests.

delete flag offensive retag edit

15 Replies

Sort by ยป oldest newest

answered 2011-07-01 10:50:31 +0800

mjablonski gravatar image mjablonski
1284 3 5
http://www.jease.org/

Hi,

ZK follows the principles of other component/event based UI toolkits (like Swing, AWT, etc.pp.) which are single threaded for good reasons. Using a dedicated event dispatching thread (or an event queue) avoids all kinds of race conditions and deadlocks. For more background please read:

http://weblogs.java.net/blog/kgh/archive/2004/10/multithreaded_t.html

Cheers, Maik

link publish delete flag offensive edit

answered 2011-07-01 14:29:23 +0800

valmar gravatar image valmar
925 2 13
http://www.timo-ernst.net

So, if I understood that correctly, it's not possible to update the UI simultaneously from different threads?

What if I put my long-running task into a separate thread, would that still block the UI?

link publish delete flag offensive edit

answered 2011-07-01 16:18:53 +0800

mjablonski gravatar image mjablonski
1284 3 5
http://www.jease.org/

If you don't need to update the UI, you can fire up an external thread and you're fine... but if you want to inform the user that the task is finished, you'll have to use the hooks ZK provides for long running operations:

http://books.zkoss.org/wiki/ZK%20Developer's%20Reference/UI%20Patterns/Long%20Operations

Cheers, Maik

link publish delete flag offensive edit

answered 2011-07-02 05:30:43 +0800

valmar gravatar image valmar
925 2 13
http://www.timo-ernst.net

I already read through that and what I basically did was the following:

- I have 1-10 possible long tasks per user (maybe even more).
- So I create a new thread for each task and let it process.
- Each thread fires an event when it's done.
- In this event handler, I call:

desktop.enableServerPush(true);
Executions.activate(desktop);
updateUI();
Executions.deactivate(desktop);

Since all threads require a different amount of time to finish, I expected the UI to be updated step-by-step.

But, that's not happening. Instead, the UI seems to:

(1) be blocked until all threads are done (click events will be delayed until all "long tasks" have finished)
and
(2) wait until all threads are done and then draws the updates (no "step-by-step" refresh).

link publish delete flag offensive edit

answered 2011-07-02 07:44:30 +0800

gekkio gravatar image gekkio flag of Finland
899 1
http://gekkio.fi/blog

You have to do

desktop.enableServerPush(true);

in the original request before starting any worker threads. In the worker thread you'll then activate/deactivate the desktop just like you mentioned.

If the UI is blocked while the threads are running, there's something wrong in your threading code and the other threads are blocking the UI thread. Could you show your code?

link publish delete flag offensive edit

answered 2011-07-02 10:01:34 +0800

valmar gravatar image valmar
925 2 13
http://www.timo-ernst.net

updated 2011-07-02 10:08:17 +0800

I'm sorry for the misunderstanding.

Of course, I first call desktop.enableServerPush(true); BEFORE running the thread.

Regarding my code: I have a Tree component which holds a list of mail accounts.
In order to refresh this, I call the following method in the main controller class:

private void synchronizeMailAccounts(final List<MailAccount> mailAccounts, boolean useThrobber) {

	for (MailAccount mailAccount : mailAccounts) {
		List<MailAccount> dummylist = new ArrayList<MailAccount>();
		dummylist.add(mailAccount);
		MailAccountSynchronizeThread thread = new MailAccountSynchronizeThread(dummylist, desktop);
		thread.addEventListener(new EventListener() {
			@Override
			public void onEvent(Event event) throws Exception {
				if (event instanceof MailAccountUpdateStatusChangedEvent) {
					MailAccountUpdateStatusChangedEvent cevent = (MailAccountUpdateStatusChangedEvent) event;
					Status status = cevent.getStatus();
					MailAccount mailAccount = cevent.getMailAccount();
					if (status.equals(Status.STARTED)) {
						try {
							Event sevent = new Event("onMailAccountProcessStarted", null, mailAccount);
							Executions.schedule(desktop, new EventListener() {
									@Override
									public void onEvent(Event event) throws Exception {
										Object data = event.getData();
										if (data != null) {
											MailAccount mailAccount = (MailAccount) data;
											Treecell cell = mailAccount.getTreecell();
											// Show that processing is in progress
											cell.setImage("/gfx/16x16/throbber.gif");
										}
									}
								}, sevent);
						} catch (Exception e) {
							e.printStackTrace();
						}
					} else if (status.equals(Status.ENDED)) {
						try {
							Event sevent = new Event("onMailAccountProcessStarted", null, mailAccount);
							Executions.schedule(desktop, new EventListener() {
									@Override
									public void onEvent(Event event) throws Exception {
										Object data = event.getData();
										if (data != null) {
											MailAccount mailAccount = (MailAccount) data;
											refreshTree(mailAccounts);
											Treecell cell = mailAccount.getTreecell();
											// Show that processing has finished
											// Strange: All cells get refreshed at once instead of one-by-one
											cell.setImage(null);
										}
									}
								}, sevent);
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
			}
		});
		desktop.enableServerPush(true);
		thread.start();
	}
}

The custom thread looks like this:

MailAccountSynchronizeThread.java

public class MailAccountSynchronizeThread extends Thread {
	public Desktop				_desktop;
	protected List<MailAccount>	_mailAccounts;
	EventListener				listener;

	public MailAccountSynchronizeThread(List<MailAccount> mailAccounts, Desktop desktop) {
		_mailAccounts = mailAccounts;
		_desktop = desktop;
	}

	public void addEventListener(EventListener eventListener) {
		listener = eventListener;
	}

	@Override
	public void run() {
		for (MailAccount mailAccount : _mailAccounts) {
			IncomingMailServer incomingServer = mailAccount.getIncomingServer();
			if (incomingServer != null) {
				try {
					listener.onEvent(new MailAccountUpdateStatusChangedEvent(mailAccount, MailAccountUpdateStatusChangedEvent.Status.STARTED));
				} catch (Exception e) {
					e.printStackTrace();
				}
				incomingServer.synchronizeFolders();
				try {
					listener.onEvent(new MailAccountUpdateStatusChangedEvent(mailAccount, MailAccountUpdateStatusChangedEvent.Status.ENDED));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}
}

Note: This code uses the Executions.schedule(...) method instead of Executions.activate(...). I tried both versions but nothing changed..

link publish delete flag offensive edit

answered 2011-07-03 01:16:44 +0800

gekkio gravatar image gekkio flag of Finland
899 1
http://gekkio.fi/blog

Everything looks fine to me. I think you should move the desktop.enableServerPush call outside the loop, but that probably won't fix your problem.

How long are your tasks? If your task lengths are small, you won't see smooth UI updates. You are also starting all the tasks roughly at the same time, so if the time differences between tasks is small, the UI updates will be clumped into a single request.

Are you using ZK EE? (= do you have zkmax.jar in your classpath). If you aren't, you are using polling server push which works differently than real comet server push.
I can't remember what the default polling interval is, but let's assume it's 2 seconds. That would mean that the UI would update every 2 seconds, and all UI updates that happen between those updates would be clumped into a single request. So if your processing time is less than 2 seconds, you would only see one update.

link publish delete flag offensive edit

answered 2011-07-03 05:10:49 +0800

valmar gravatar image valmar
925 2 13
http://www.timo-ernst.net

Well, the time a task required to finish differs. There are accounts which can be refreshed within 1-2 seconds and there are some which take 5-10 seconds.

I am not using ZK EE. I use PE, so I guess I am using polling server push.
However, 5-10 seconds for the "biggest" tasks should be more than enough to force a UI update, especially if I simultaneously check 7 accounts at the same time.

link publish delete flag offensive edit

answered 2011-07-04 15:38:52 +0800

gekkio gravatar image gekkio flag of Finland
899 1
http://gekkio.fi/blog

Hmm, I'm running out of ideas.
Could you test your program temporarily with ZK EE evaluation version? If the problem persists, we can be sure that the polling server push is not the cause for this problem.
You only need to add the evaluation versions of zkex.jar and zkmax.jar to your classpath.

Also..are you using the latest ZK (5.0.7.1) or an older version?

link publish delete flag offensive edit

answered 2011-07-04 17:48:49 +0800

valmar gravatar image valmar
925 2 13
http://www.timo-ernst.net

updated 2011-07-04 17:50:55 +0800

gekkio wrote:

Hmm, I'm running out of ideas.

Same here xD

I completely removed all threads from my application now because I couldn't find the cause for the trouble. Instead, I handle everything in the main thread now which really reduces responsiveness and usability at this point but I don't see being able to fix this very soon :-(

I also realized that using

new Thread(){...}
isn't the best way to create threads in a web application, which means that I'd have to take care about that too, see:
http://stackoverflow.com/questions/6548261/good-or-bad-idea-multiple-threads-in-a-multi-user-servlet-based-web-application
..and release date is coming closer ;-)

I'll give your suggestion using comet push a try once I've implemented a multi-threaded version again.

Ps: Yeah, I am using 5.0.7.1

Pps: In the code that I posted above, there are calls of

refreshTree(mailAccounts);

and
incomingServer.synchronizeFolders();

...the implementation of which I didn't post here yet.
I somehow have the feeling that the code in there might block my main servlet thread..

link publish delete flag offensive edit
Your reply
Please start posting your answer anonymously - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a substantial answer, for discussions, please use comments and please do remember to vote (after you log in)!

[hide preview]

Question tools

Follow

RSS

Stats

Asked: 2011-07-01 09:35:29 +0800

Seen: 721 times

Last updated: Aug 09 '11

Support Options
  • Email Support
  • Training
  • Consulting
  • Outsourcing
Learn More