Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "E4/EAS/Progress Service"

< E4‎ | EAS
(Eclipse 3.x API)
(e4 (Java))
Line 122: Line 122:
 
Idea 1:  Status returning runnable with progress (like Job)
 
Idea 1:  Status returning runnable with progress (like Job)
 
<source lang="java">
 
<source lang="java">
 +
@Inject Provider<RunnableContext> runnableContext;
 +
 
IRunnable runnable = new Runnable("My long-running operation...") {
 
IRunnable runnable = new Runnable("My long-running operation...") {
 
   protected IStatus run(IProgressMonitor monitor) {
 
   protected IStatus run(IProgressMonitor monitor) {
Line 138: Line 140:
 
   }
 
   }
 
};
 
};
 +
runnableContext.get().run(runnable);
 
</source>
 
</source>
  
This pattern requires a RunnableContext who knows how to run the runnable (fork, cancelable, UI-thread, etc), get the required monitor, and report the status.  This is easiest for migrating 3.x jobs.
+
This pattern requires a RunnableContext who knows how to run the runnable (fork, cancelable, UI-thread, etc), pass the appropriate monitor to the runnable, and report the returned status.
  
 
Idea 2:  Simple runnable that obtains progress and status from injected services.
 
Idea 2:  Simple runnable that obtains progress and status from injected services.
Line 162: Line 165:
 
   }
 
   }
 
};
 
};
 +
runnableContext.get().run(runnable);
 
</source>
 
</source>
  
 
This pattern still requires a RunnableContext for controlling how the code is run (fork, cancelable, UI-thread, modal, etc.), but the client code is injected with appropriate status and progress services.   
 
This pattern still requires a RunnableContext for controlling how the code is run (fork, cancelable, UI-thread, modal, etc.), but the client code is injected with appropriate status and progress services.   
  
The main idea here is that the client runnable is separate from how progress and errors are reported, and you shouldn't have to know in advance when writing the code how your code will be run.
+
The main win is that the runnable code ('''what''') is the same no matter how progress and errors are reported.  Complex scenarios involving progress wrapping are handled outside of the client.
  
 
The runnable context defines  
 
The runnable context defines  
Line 173: Line 177:
 
** Modal (forked but blocking on result)  
 
** Modal (forked but blocking on result)  
 
** Asynchronously (in the background)
 
** Asynchronously (in the background)
* How progress is reported
+
* how to get progress and status reporting
** Provide UI-thread safe monitors where needed
+
* deal with UI-thread safety issues
* How status is reported
+
 
* Locks and scheduling rules (ISchedulingRule)
 
* Locks and scheduling rules (ISchedulingRule)
 
* Batching of operations/notifications (IWorkspaceOperation)
 
* Batching of operations/notifications (IWorkspaceOperation)
Line 184: Line 187:
 
* jobs (not tied to any visible part, centralized error and progress reporting)
 
* jobs (not tied to any visible part, centralized error and progress reporting)
 
* futures (same as above with post-processing)
 
* futures (same as above with post-processing)
 
'''Questions:'''
 
* Is there really a runnable context (implemented by various UI elements such as parts, wizards, etc.)  Who is told to run the runnable and how? 
 
* Is a localized status and progress reporting service inferred by a particular kind of runnable context or is it injected, or is it obtained from the IEclipseContext?
 
  
  
 
Need to review the AC's [[Architecture Council/Minutes May 15 2008#Becoming More Asynchronous|meeting]] [[Architecture Council/Minutes May 15 2008#Jobs and Scheduling_Rules|minutes]] prior to the creation of this e4 API.
 
Need to review the AC's [[Architecture Council/Minutes May 15 2008#Becoming More Asynchronous|meeting]] [[Architecture Council/Minutes May 15 2008#Jobs and Scheduling_Rules|minutes]] prior to the creation of this e4 API.

Revision as of 18:24, 29 October 2009

Based on a user's interaction with an application, the application may wish to schedule work to be done by other worker threads. The work that is done on these other worker threads may or may not be presented to the user. If background work is being invoked automatically based on some conditions, it may not necessarily be useful to present this information to the user. However, if the user explicitly invokes an action that causes a long-running operation to begin, they may wish to know about this so that they can respond accordingly when the operation has completed or just cancel the operation outright.

Eclipse 3.x API

In Eclipse 3.x, there are a variety of classes and interfaces that exposes progress reporting and work scheduling features.

  • org.eclipse.jface.operation.IRunnableContext
  • org.eclipse.ui.progress.IProgressService
  • org.eclipse.ui.progress.IWorkbenchSiteProgressService
  • org.eclipse.jface.operation.IRunnableWithProgress
  • org.eclipse.core.runtime.jobs.Job

Suppose an application wants to do some work, reporting some progress and errors. The essential application code (what is being done) is

monitor.beginTask("foo", 100);
try {
   doFirstHalfFoo();
   monitor.worked(50);
   doSecondHalfFoo();
} catch (FooException e) {
   status = new Status(IStatus.ERROR, "id", "foo failure message", e);
} finally {
   monitor.done();
}

The code structure depends on how the code is run. For scheduling work to be completed by a worker thread, the Jobs framework is used.

Job aJob = new Job("My long-running operation...") {
  protected IStatus run(IProgressMonitor monitor) {
  IStatus status = Status.OK_STATUS;
    monitor.beginTask("foo", 100);
    try {
       doFirstHalfFoo();
       monitor.worked(50);
       doSecondHalfFoo();
    } catch (FooException e) {
       status = new Status(IStatus.ERROR, "id", "foo failure message", e);
    } finally {
       monitor.done();
    }
    return status;  }
};
// true to indicate that this job was initiated by a UI end user
aJob.setUser(true);

To show progress for the same work in a wizard, IRunnableWithProgress is used.

IRunnableWithProgress runnable = new IRunnableWithProgress() {
  public void run(IProgressMonitor monitor) {
    monitor.beginTask("foo", 100);
    try {
       doFirstHalfFoo();
       monitor.worked(50);
       doSecondHalfFoo();
    } catch (FooException e) {
       status = new Status(IStatus.ERROR, "id", "foo failure message", e);
       StatusManager.getManager().handle(status, StatusManager.SHOW);
    } finally {
       monitor.done();
    }
   }
};
try {
   getContainer().run((true, true, runnable);
} catch (InvocationTargetException) {
} catch (InterruptedException) {
}

Things get more complicated as soon as you deviate from the expected usage pattern. Suppose you want to report the progress of your job somewhere in addition to the progress view, and report errors locally. This has to happen in the client code.

Job aJob = new Job("My long-running operation...") {
  protected IStatus run(IProgressMonitor monitor) {
    IStatus status = Status.OK_STATUS;
    IProgressMonitor wrappedMonitor = new ProgressMonitorWrapper(monitor) {
       // override everything so that you can report it locally, too
        public void beginTask(String name, int totalWork) {
		super.beginTask(name, totalWork);
                someLocalMonitor.beginTask(name, totalWork);
	}
    };
    wrappedMonitor.beginTask("foo", 100);
    try {
       doFirstHalfFoo();
       wrappedMonitor.worked(50);
       doSecondHalfFoo();
    } catch (FooException e) {
       status = new Status(IStatus.ERROR, "id", "foo failure message", e);
    } finally {
       wrappedMonitor.done();
    }
    return status;  }
};
// true to indicate that this job was initiated by a UI end user
aJob.setUser(true);
// don't report errors in a separate dialog
aJob.setProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY, Boolean.TRUE);
// report errors the way I want to
aJob.addJobListener(new JobChangeAdapter() {
  public void done(JobChangeEvent event) {
    if (!status.isOK()) {
      updateStatus(event.getResult());
    }
  }
});

Critique

  • More code for how than what.
  • Code duplication since it's not easy to define what independently from how. See Bug 293098.
  • Jobs and wizards/dialogs don't play well together
    • Need to wrap the monitor if you run a job or user can't see what's going on.
    • You can get unexpected blocking dialogs (See Bug 276904)
    • Some constructs use jobs and provide no way to redirect progress. (DeferredTreeContentManager is IWorkbenchSiteProgressService savvy but can't show progress elsewhere and there's no easy way to wrap the monitor).
  • If you run some operation synchronously and want to report failure, you have to decide in-line whether its logged, shown, or both, but that decision is not always known on the calling side.
  • You can get broken if you call code that forks a job deep in the bowels of its implementation and your monitor wasn't expecting it. See Bug 282333 and Bug 244246.

e4 (Java)

Idea 1: Status returning runnable with progress (like Job)

@Inject Provider<RunnableContext> runnableContext;
 
IRunnable runnable = new Runnable("My long-running operation...") {
  protected IStatus run(IProgressMonitor monitor) {
    IStatus status = Status.OK_STATUS;
    monitor.beginTask("foo", 100);
    try {
       doFirstHalfFoo();
       monitor.worked(50);
       doSecondHalfFoo();
    } catch (FooException e) {
       status = new Status(IStatus.ERROR, "id", "foo failure message", e);
    } finally {
       monitor.done();
    }
    return status;
  }
};
runnableContext.get().run(runnable);

This pattern requires a RunnableContext who knows how to run the runnable (fork, cancelable, UI-thread, etc), pass the appropriate monitor to the runnable, and report the returned status.

Idea 2: Simple runnable that obtains progress and status from injected services.

@Inject Provider<StatusHandler> statusHandler;
@Inject Provider<RunnableContext> runnableContext;
IRunnable runnable = new Runnable("My long-running operation...") {
  protected IStatus run() {
    IProgressMonitor monitor = runnableContext.get().getProgressMonitor();
    monitor.beginTask("foo", 100);
    try {
       doFirstHalfFoo();
       monitor.worked(50);
       doSecondHalfFoo();
    } catch (FooException e) {
       statusHandler.get().handle(e, "foo failure message", runnableContext.get().getStatusReportingService()); 
    } finally {
       monitor.done();
    }
  }
};
runnableContext.get().run(runnable);

This pattern still requires a RunnableContext for controlling how the code is run (fork, cancelable, UI-thread, modal, etc.), but the client code is injected with appropriate status and progress services.

The main win is that the runnable code (what) is the same no matter how progress and errors are reported. Complex scenarios involving progress wrapping are handled outside of the client.

The runnable context defines

  • how something is run.
    • Synchronous (in current thread)
    • Modal (forked but blocking on result)
    • Asynchronously (in the background)
  • how to get progress and status reporting
  • deal with UI-thread safety issues
  • Locks and scheduling rules (ISchedulingRule)
  • Batching of operations/notifications (IWorkspaceOperation)

Example contexts include:

  • wizard or dialog modal context (local progress and error reporting)
  • parts (adornment when busy or local progress, centralized error reporting via dialog?)
  • operations on a shared set of resources
  • jobs (not tied to any visible part, centralized error and progress reporting)
  • futures (same as above with post-processing)


Need to review the AC's meeting minutes prior to the creation of this e4 API.

Back to the top