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

E4/EAS/Progress Service

< E4‎ | EAS
Revision as of 17:43, 29 October 2009 by Susan franklin.us.ibm.com (Talk | contribs) (e4 (Java))

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

Typically, 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);

In the IProgressService and IWorkbenchSiteProgressService case, these are queried from the local service locator.

Critique

  • You need to know how your code is going to be used (in a worker thread) at the time you create it (I need a Job).
  • You need to decide in the client code whether the user sees the job
  • We have no headless IRunnableWithProgress. (Today we have JFace IRunnableWithProgress, Job.run(IProgressMonitor), IDE's WorkspaceModifyOperation, Equinox future's IProgressRunnable). If we had a core-level IRunnableWithProgress then applications could define runnables that could be run in different runnable contexts (UI modal context, user job, future, etc.) Today the application has to define its own operations framework to handle these differences.
  • There is no way to ensure that the runnables can be run in different runnable contexts (ie, a user job) with progress monitors and error reporting that is appropriate for where they are triggered. See Bug 293098.
  • Jobs and wizards/dialogs don't play well together
    • If you a run a job in a modal dialog, there is no jobs progress dialog and your only progress indicator is the status bar unless you happen to have the progress view open.
    • You can get weird blocking dialogs (See Bug 276904)
    • Some constructs (DeferredTreeContentManager) use jobs and only know about certain progress contexts. If I run a deferred fetch in a site, the site is adorned and the job progress is reported normally. If I run the same viewer in a dialog, there is no local progress.
  • 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.
  • If you want to show progress in multiple places, you have to wrap the monitor. If you are using jobs this means defining your own framework so you can intercept the monitor on the run method and wrap it.
  • 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.

The first example of the client code looks simple until you deviate from the expected usage pattern. Suppose you now want to report the progress of your job locally in addition to the progress view. 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);

If the client doesn't know the context that the code will be run in, more tricks are needed to get the progress right (ie, a jobs hierarchy that lets clients set the additional monitors and job properties)

e4 (Java)

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

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;
  }
};

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.

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();
    }
  }
};

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 runnable context defines

  • how something is run.
    • Synchronous (in current thread)
    • Modal (forked but blocking on result)
    • Asynchronously (in the background)
  • How progress is reported
    • Provide UI-thread safe monitors where needed
  • How status is reported
  • 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)

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 meeting minutes prior to the creation of this e4 API.

Back to the top