runWriteActionWithProgressInBackgroundThread

This commit is contained in:
peter
2016-12-06 14:49:57 +01:00
parent 7bda896e1b
commit 3d7b004cd0
3 changed files with 119 additions and 42 deletions
@@ -30,16 +30,22 @@ public class TestWriteActionUnderProgress extends DumbAwareAction {
@Override
public void actionPerformed(AnActionEvent e) {
ApplicationImpl app = (ApplicationImpl)ApplicationManager.getApplication();
app.runWriteActionWithProgress("Progress", null, null, null, TestWriteActionUnderProgress::runIndeterminateProgress);
app.runWriteActionWithProgress("Cancellable Progress", null, null, "Stop", TestWriteActionUnderProgress::runDeterminateProgress);
boolean success = app.runWriteActionWithProgressInDispatchThread(
"Progress", null, null, null,
TestWriteActionUnderProgress::runIndeterminateProgress);
assert success;
app.runWriteActionWithProgressInBackgroundThread("Cancellable Progress", null, null, "Stop", TestWriteActionUnderProgress::runDeterminateProgress);
}
private static void runDeterminateProgress(ProgressIndicator indicator) {
indicator.setIndeterminate(false);
int iterations = 3000;
indicator.setText("");
indicator.setText("In background thread");
for (int i = 0; i < iterations; i++) {
TimeoutUtil.sleep(1);
ApplicationManager.getApplication().assertWriteAccessAllowed();
indicator.setFraction(((double)i + 1) / ((double)iterations));
indicator.setText2(String.valueOf(i));
ProgressManager.checkCanceled();
@@ -48,9 +54,10 @@ public class TestWriteActionUnderProgress extends DumbAwareAction {
private static void runIndeterminateProgress(ProgressIndicator indicator) {
indicator.setIndeterminate(true);
indicator.setText("Indeterminate");
indicator.setText("In Event Dispatch thread");
for (int i = 0; i < 1000; i++) {
TimeoutUtil.sleep(5);
ApplicationManager.getApplication().assertWriteAccessAllowed();
indicator.checkCanceled();
}
}
@@ -72,6 +72,7 @@ import com.intellij.util.concurrency.Semaphore;
import com.intellij.util.containers.Stack;
import com.intellij.util.io.storage.HeavyProcessLatch;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
@@ -109,6 +110,7 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App
private final Stack<Class> myWriteActionsStack = new Stack<>(); // accessed from EDT only, no need to sync
private int myWriteStackBase = 0;
private volatile Thread myWriteActionThread;
private int myInEditorPaintCounter; // EDT only
private final long myStartTime;
@@ -690,6 +692,10 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App
@Override
@NotNull
public ModalityState getCurrentModalityState() {
if (Thread.currentThread() == myWriteActionThread) {
return getDefaultModalityState();
}
return LaterInvocator.getCurrentModalityState();
}
@@ -942,22 +948,41 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App
myLock.readUnlock();
}
public boolean runWriteActionWithProgress(@NotNull String title,
@Nullable Project project,
@Nullable JComponent parentComponent,
@Nullable String cancelText,
@NotNull Consumer<ProgressIndicator> action) {
@ApiStatus.Experimental
public boolean runWriteActionWithProgressInDispatchThread(
@NotNull String title, @Nullable Project project, @Nullable JComponent parentComponent, @Nullable String cancelText,
@NotNull Consumer<ProgressIndicator> action
) {
Class<?> clazz = action.getClass();
startWrite(clazz);
try {
PotemkinProgress indicator = new PotemkinProgress(title, project, parentComponent, cancelText);
try {
ProgressManager.getInstance().runProcess(() -> action.consume(indicator), indicator);
}
catch (ProcessCanceledException ignore) { }
finally {
indicator.progressFinished();
}
indicator.runInSwingThread(() -> action.consume(indicator));
return !indicator.isCanceled();
}
finally {
endWrite(clazz);
}
}
@ApiStatus.Experimental
public boolean runWriteActionWithProgressInBackgroundThread(
@NotNull String title, @Nullable Project project, @Nullable JComponent parentComponent, @Nullable String cancelText,
@NotNull Consumer<ProgressIndicator> action
) {
Class<?> clazz = action.getClass();
startWrite(clazz);
try {
PotemkinProgress indicator = new PotemkinProgress(title, project, parentComponent, cancelText);
indicator.runInBackground(() -> {
assert myWriteActionThread == null;
myWriteActionThread = Thread.currentThread();
try {
action.consume(indicator);
} finally {
myWriteActionThread = null;
}
});
return !indicator.isCanceled();
}
finally {
@@ -1035,7 +1060,10 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App
@Override
public boolean isReadAccessAllowed() {
return isDispatchThread() || myLock.isReadLockedByThisThread();
if (isDispatchThread()) {
return myWriteActionThread == null; // no reading from EDT during background write action
}
return myLock.isReadLockedByThisThread() || myWriteActionThread == Thread.currentThread();
}
@Override
@@ -1140,7 +1168,9 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App
}
private void startWrite(@NotNull Class clazz) {
assertIsDispatchThread("Write access is allowed from event dispatch thread only");
if (!isWriteAccessAllowed()) {
assertIsDispatchThread("Write access is allowed from event dispatch thread only");
}
HeavyProcessLatch.INSTANCE.stopThreadPrioritizing(); // let non-cancellable read actions complete faster, if present
boolean writeActionPending = myWriteActionPending;
if (gatherStatistics && myWriteActionsStack.isEmpty() && !writeActionPending) {
@@ -1283,7 +1313,7 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App
@Override
public boolean isWriteAccessAllowed() {
return isDispatchThread() && myLock.isWriteLocked();
return isDispatchThread() && myLock.isWriteLocked() || myWriteActionThread == Thread.currentThread();
}
@Override
@@ -15,11 +15,12 @@
*/
package com.intellij.openapi.progress.util;
import com.intellij.concurrency.JobScheduler;
import com.intellij.ide.IdeEventQueue;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.concurrency.Semaphore;
import com.intellij.util.io.storage.HeavyProcessLatch;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -29,30 +30,27 @@ import javax.swing.*;
import java.awt.*;
import java.awt.event.InputEvent;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* A progress indicator for processes running in EDT. Paints itself in checkCanceled calls.
* A progress indicator for write actions. Paints itself explicitly, without resorting to normal Swing's delayed repaint API.
* Doesn't dispatch Swing events, except for handling manually those that can cancel it or affect the visual presentation.
*
* @author peter
*/
public class PotemkinProgress extends ProgressWindow {
private long myLastUiUpdate = System.currentTimeMillis();
private final Queue<InputEvent> myEventQueue = new ConcurrentLinkedQueue<>();
private final LinkedBlockingQueue<InputEvent> myEventQueue = new LinkedBlockingQueue<>();
public PotemkinProgress(@NotNull String title, @Nullable Project project, @Nullable JComponent parentComponent, @Nullable String cancelText) {
super(cancelText != null,false, project, parentComponent, cancelText);
setTitle(title);
installCheckCanceledPaintingHook();
ApplicationManager.getApplication().assertIsDispatchThread();
startStealingInputEvents();
}
private void startStealingInputEvents() {
checkNativeEventsRegularly();
IdeEventQueue.getInstance().addPostEventListener(event -> {
if (event instanceof InputEvent) {
myEventQueue.offer((InputEvent)event);
@@ -62,12 +60,6 @@ public class PotemkinProgress extends ProgressWindow {
}, this);
}
private void checkNativeEventsRegularly() {
ScheduledFuture<?> future = JobScheduler.getScheduler().scheduleWithFixedDelay(
() -> SunToolkit.flushPendingEvents(), 3, 3, TimeUnit.MILLISECONDS);
Disposer.register(this, () -> future.cancel(false));
}
@NotNull
@Override
protected ProgressDialog getDialog() {
@@ -83,7 +75,7 @@ public class PotemkinProgress extends ProgressWindow {
@Override
public boolean isCanceled() {
if (ApplicationManager.getApplication().isDispatchThread()) {
dispatchAwtEventsWithoutModelAccess();
dispatchAwtEventsWithoutModelAccess(0);
updateUI();
}
return super.isCanceled();
@@ -91,12 +83,18 @@ public class PotemkinProgress extends ProgressWindow {
});
}
private void dispatchAwtEventsWithoutModelAccess() {
while (true) {
InputEvent event = myEventQueue.poll();
if (event == null) return;
private void dispatchAwtEventsWithoutModelAccess(int timeoutMs) {
SunToolkit.flushPendingEvents();
try {
while (true) {
InputEvent event = myEventQueue.poll(timeoutMs, TimeUnit.MILLISECONDS);
if (event == null) return;
dispatchInputEvent(event);
dispatchInputEvent(event);
}
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@@ -147,7 +145,7 @@ public class PotemkinProgress extends ProgressWindow {
return true;
}
public void progressFinished() {
private void progressFinished() {
getDialog().hideImmediately();
}
@@ -163,4 +161,46 @@ public class PotemkinProgress extends ProgressWindow {
dialogPanel.paintImmediately(dialogPanel.getBounds());
}
/** Executes the action in EDT, paints itself inside checkCanceled calls. */
public void runInSwingThread(@NotNull Runnable action) {
ApplicationManager.getApplication().assertIsDispatchThread();
installCheckCanceledPaintingHook();
try {
ProgressManager.getInstance().runProcess(action, this);
}
catch (ProcessCanceledException ignore) { }
finally {
progressFinished();
}
}
/** Executes the action in a background thread, block Swing thread, handles selected input events and paints itself periodically. */
public void runInBackground(@NotNull Runnable action) {
ApplicationManager.getApplication().assertIsDispatchThread();
enterModality();
try {
ensureBackgroundThreadStarted(action);
while (isRunning()) {
dispatchAwtEventsWithoutModelAccess(10);
updateUI();
}
}
finally {
exitModality();
progressFinished();
}
}
private void ensureBackgroundThreadStarted(@NotNull Runnable action) {
Semaphore started = new Semaphore();
started.down();
ApplicationManager.getApplication().executeOnPooledThread(() -> ProgressManager.getInstance().runProcess(() -> {
started.up();
action.run();
}, this));
started.waitFor();
}
}