MVC Progress Bar Threading

MVC Progress Bar Threading

Don't call

_progressBar.setValue(_model.getStatus());

from within your SwingWorker as this is calling Swing code from a background thread and is what the PropertyChangeListener is for anyway. Instead, just set the progress property, that's all.

Also, don't call done() from within the doInBackground method as this needs to be called from the EDT by the SwingWorker. So let the SwingWorker itself call this method when it is in fact done.

Also, Done() should be done() -- the first letter shouldn't be capitalized, and you should use @Override annotations in this code so you can be sure that you're overriding methods correctly.

Also, what does this do?

 _model.startSearch(_view.getTerm());

Does it call code that takes a while to complete? Should this be initialized from within the SwingWorker doInBackground itself?

Edit:
Another option is to give the Model a bound int property, say called progress, and then add a PropertyChangeListener to it directly letting it update the JProgressBar. For example,

import java.awt.BorderLayout;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import javax.swing.*;

public class MVC_ProgressBarThread {
private static void createAndShowUI() {
MVC_View view = new MVC_View();
MVC_Model model = new MVC_Model();
MVC_Control control = new MVC_Control(view, model);
view.setControl(control);

JFrame frame = new JFrame("MVC_ProgressBarThread");
frame.getContentPane().add(view);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}

public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}

@SuppressWarnings("serial")
class MVC_View extends JPanel {
private MVC_Control control;
private JProgressBar progressBar = new JProgressBar();
private JButton startActionButton = new JButton("Start Action");

public MVC_View() {
startActionButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
buttonActionPerformed();
}
});

JPanel buttonPanel = new JPanel();
buttonPanel.add(startActionButton);
setLayout(new BorderLayout());
add(buttonPanel, BorderLayout.NORTH);
add(progressBar, BorderLayout.CENTER);
}

public void setControl(MVC_Control control) {
this.control = control;
}

private void buttonActionPerformed() {
if (control != null) {
control.doButtonAction();
}
}

public void setProgress(int progress) {
progressBar.setValue(progress);
}

public void start() {
startActionButton.setEnabled(false);
}

public void done() {
startActionButton.setEnabled(true);
setProgress(100);
}
}

class MVC_Control {
private MVC_View view;
private MVC_Model model;

public MVC_Control(final MVC_View view, final MVC_Model model) {
this.view = view;
this.model = model;
model.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent pce) {
if (MVC_Model.PROGRESS.equals(pce.getPropertyName())) {
view.setProgress((Integer)pce.getNewValue());
}
}
});
}

public void doButtonAction() {
view.start();
SwingWorker<Void, Void> swingworker = new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
model.reset();
model.startSearch();
return null;
}

@Override
protected void done() {
view.done();
}
};
swingworker.execute();
}

}

class MVC_Model {
public static final String PROGRESS = "progress";
private static final int MAX = 100;
private static final long SLEEP_DELAY = 100;
private int progress = 0;
private PropertyChangeSupport pcs = new PropertyChangeSupport(this);

public void setProgress(int progress) {
int oldProgress = this.progress;
this.progress = progress;

PropertyChangeEvent evt = new PropertyChangeEvent(this, PROGRESS, oldProgress, progress);
pcs.firePropertyChange(evt);
}

public void reset() {
setProgress(0);
}

public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}

public void startSearch() {
for (int i = 0; i < MAX; i++) {
int newValue = (100 * i) / MAX;
setProgress(newValue);
try {
Thread.sleep(SLEEP_DELAY);
} catch (InterruptedException e) {}
}
}
}

Change view dynamically from controller - Progress bar with threading in Rails

Yes it's a wrong approach.

The Web is stateless, and you can't block a process to render view and get it after by another request.

The good way is :

1) Generate a queue tasking instead of your thread. You can use delayed_job, resque or queue system, like rabbitMQ or beanstalkD

2) Add a controller to check if the process in your queue is end and how is progress. Inside, you put the queue id and check if is end or not and return the progress percent.

The easiest way is do that by Ajax.

MVC progress bar won't work correctly

This is an instance of an old problem, see here:

http://msdn.microsoft.com/en-us/library/ms178581.aspx

Access to ASP.NET session state is exclusive per session, which means
that if two different users make concurrent requests, access to each
separate session is granted concurrently. However, if two concurrent
requests are made for the same session (by using the same SessionID
value), the first request gets exclusive access to the session
information. The second request executes only after the first request
is finished. (The second session can also get access if the exclusive
lock on the information is freed because the first request exceeds the
lock time-out.) If the EnableSessionState value in the @ Page
directive is set to ReadOnly, a request for the read-only session
information does not result in an exclusive lock on the session data.
However, read-only requests for session data might still have to wait
for a lock set by a read-write request for session data to clear.

I think you can find numerous posts on SO that discuss this. Here is one:

Asynchronous Controller is blocking requests in ASP.NET MVC through jQuery

and here is one workaround:

Disable Session state per-request in ASP.Net MVC

Ok adding some (untested!) example code. This is just to indicate how to solve this:

public class myCache {

private static Dictionary<string, ProgressInfo> _pinfos = new Dictionary<string, ProgressInfo>();
private static readonly object _lockObject = new object();

public static void Add(string key, ProgressInfo value)
{
lock (_lockObject)
{
if (!_pinfos.ContainsKey(key)) {
_pinfos.Add(key, value);
}
else {
_pinfos[key] = value;
}

}
}

public static ProgressInfo Get(string key) {
if (_pinfos.ContainsKey(key)) {
return _pinfos[key];
}
else {
return null;
}
}

public static void Remove(string key) {
lock (_lockObject)
{
if (_pinfos.ContainsKey(key)) {
_pinfos.Remove(key);
}
}
}
}

[SessionState(SessionStateBehavior.ReadOnly)]
public class TestController : AsyncController

public void DoCalculation(string id)
{
ProgressInfo progress = new ProgressInfo();
if (!string.IsNullOrEmpty(id)) {
myCache.Add(id,progress);
}

//periodicly update progress
int i = 0;
while(i == 0 && progress.Percent != 7600000340)
{
progress.Percent++;
Thread.Sleep(1000);
}
}

public JsonResult GetProgress(string id)
{
ProgressInfo progress;
if (string.IsNullOrEmpty(id)
|| (progress = myCache.Get(id)) == null)
{
return Json(new
{
success = false
});
}
if (progress.Done)
{
myCache.Remove(id);
}
return Json(new
{
success = true,
done = progress.Done,
percent = progress.Percent
});
}
}

show progress of multiple threads (generated by spring async) in html page

I ended up with 2 answers which i found in :
first helpful answer i could not understand it first cause i didn't know what is ajax at that time, but after spending some time i used it with some modification to make it simple
in my jsp i added my progress bar div :

<div class="row">
<div class="progress">
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" aria-valuenow="70"
aria-valuemin="0" aria-valuemax="100" style="width:0%;">
<div id="label">0%</div>
</div>
</div>
</div>

you can find some description of progress element and how to style it in progress element style

then i added jquery script as below

<script>
$(document).ready(function () {

var width = 0;
var id = setInterval(frame, 5000);

function frame() {
if (width >= 100) {
console.log("task is completed"+width);
clearInterval(id);
window.location.href = "completed_url";
} else {
console.log(width);
$.ajax({
url: "/progress",
success: function (progress) {
$('#progressBar').css('width', progress + '%');
document.getElementById("label").innerHTML = progress * 1 + '%';
width = progress;
},
});
}
}

});
</script>

note that the setInterval(frame, 5000) will execute frame function every 5 sec. i choose 5 to not consume my connections to Db.

ajax function will send get request to the url specified, if the success response it assign the return value to progress variable in success: function (progress), you can find a link to the most popular way to do an http request in java script here

to move the bar i used .css function.

to increase lable value i used document.getElementById("label").innerHTML.

in my controller:

    @RequestMapping(value = "/progress", method = GET)
public @ResponseBody
int getProgress() {
return myService.getProgress();
}

whenever a task is executed by task executor with @Async its status will be updated tin db, getProgress() function will calculate the progress from db.

the second way without query from db id to use use atomic integer to hold the progress value, but it seems to work for one task. I dont know how to differentiate between multiple requests to task executor if exists.

progress bars + MVC in Java =?

No (to 1) and NOOOO (to 2). At least in my opinion.

No (to 1): First, DefaultBoundedRangeModel is a javax.swing class. In my opinion, these classes have no place in models. For example, think about the model living on the server, being accessed via RMI - All of the sudden putting a javax.swing class there seems "not right".
However, the real problem is that you're giving a part of your model (the bounded model) to someone else, with no control over events fired or queries made.

No (to 2): Ugh. Binding is fun but (at least in my opinion) should be used to synchronize between UI model and UI components, not between data model and UI model. Again, think what would happen if your data model lived on a remote server, accessed by RMI.

So what? Well, this is only a suggestion, but I'd add an event listener interface and add the standard event listener subscription methods (addListner(...), removeListener(...)). I'd call these listeners from within my model when I have updates going on. Of course, I'd make sure to document the calling thread (or say it cannot be determined) in order for the client (the UI in this case) to be able to synchronize correctly (invokeLater and friends). Since the listener service will be exposed by the controller, this will allow the model to live anywhere (even allowing for listeners to be remotely invoked or pooled). Also, this would decouple the model from the UI, making it possible to build more models containing it (translators / decorators / depending models).

Hope this helps.



Related Topics



Leave a reply



Submit