/* * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.openapi.progress.util; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.EmptyProgressIndicator; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.EmptyRunnable; import com.intellij.openapi.util.Ref; import com.intellij.util.Consumer; import com.intellij.util.Function; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import consulo.annotations.RequiredDispatchThread; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; public class BackgroundTaskUtil { private static final Logger LOG = Logger.getInstance(BackgroundTaskUtil.class); private static final Runnable TOO_SLOW_OPERATION = new EmptyRunnable(); /* * Executor to perform <possibly> long operations on pooled thread * It can be used to reduce blinking if background task completed fast. In this case callback will be called without invokeLater(). * * Simple approach: * * onSlowAction.run() // show "Loading..." * executeOnPooledThread({ * Runnable callback = backgroundTask(); // some background computations * invokeLater(callback); // apply changes * }); * * will lead to "Loading..." visible between current moment and execution of invokeLater() event. * This period can be very short and looks like 'jumping' if background operation is fast. */ @RequiredDispatchThread @NotNull public static ProgressIndicator executeAndTryWait(@NotNull final Function<ProgressIndicator, Runnable> backgroundTask, @Nullable final Runnable onSlowAction, final int waitMillis) { return executeAndTryWait(backgroundTask, onSlowAction, waitMillis, false); } @RequiredDispatchThread @NotNull public static ProgressIndicator executeAndTryWait(@NotNull final Function<ProgressIndicator, Runnable> backgroundTask, @Nullable final Runnable onSlowAction, final int waitMillis, final boolean forceEDT) { final ModalityState modality = ModalityState.current(); final ProgressIndicator indicator = new EmptyProgressIndicator() { @NotNull @Override public ModalityState getModalityState() { return modality; } }; final Semaphore semaphore = new Semaphore(0); final AtomicReference<Runnable> resultRef = new AtomicReference<Runnable>(); if (forceEDT) { try { Runnable callback = backgroundTask.fun(indicator); finish(callback, indicator); } catch (ProcessCanceledException ignore) { } catch (Throwable t) { LOG.error(t); } } else { ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { @Override public void run() { ProgressManager.getInstance().executeProcessUnderProgress(new Runnable() { @Override public void run() { final Runnable callback = backgroundTask.fun(indicator); if (indicator.isCanceled()) { semaphore.release(); return; } if (!resultRef.compareAndSet(null, callback)) { ApplicationManager.getApplication().invokeLater(new Runnable() { @Override public void run() { finish(callback, indicator); } }, modality); } semaphore.release(); } }, indicator); } }); try { semaphore.tryAcquire(waitMillis, TimeUnit.MILLISECONDS); } catch (InterruptedException ignore) { } if (!resultRef.compareAndSet(null, TOO_SLOW_OPERATION)) { // update presentation in the same thread to reduce blinking, caused by 'invokeLater' and fast background operation finish(resultRef.get(), indicator); } else { if (onSlowAction != null) onSlowAction.run(); } } return indicator; } @RequiredDispatchThread private static void finish(@NotNull Runnable result, @NotNull ProgressIndicator indicator) { if (indicator.isCanceled()) return; result.run(); indicator.stop(); } @RequiredDispatchThread @Nullable public static <T> T tryComputeFast(@NotNull final Function<ProgressIndicator, T> backgroundTask, final int waitMillis) { final Ref<T> resultRef = new Ref<T>(); ProgressIndicator indicator = executeAndTryWait(new Function<ProgressIndicator, Runnable>() { @Override public Runnable fun(ProgressIndicator indicator1) { final T result = backgroundTask.fun(indicator1); return new Runnable() { @Override public void run() { resultRef.set(result); } }; } }, null, waitMillis, false); indicator.cancel(); return resultRef.get(); } @RequiredDispatchThread @NotNull public static ProgressIndicator executeOnPooledThread(@NotNull Consumer<ProgressIndicator> task, @NotNull Disposable parent) { final ModalityState modalityState = ModalityState.current(); return executeOnPooledThread(task, parent, modalityState); } @NotNull public static ProgressIndicator executeOnPooledThread(@NotNull final Runnable runnable, @NotNull Disposable parent) { return executeOnPooledThread(new Consumer<ProgressIndicator>() { @Override public void consume(ProgressIndicator indicator) { runnable.run(); } }, parent, ModalityState.NON_MODAL); } @NotNull public static ProgressIndicator executeOnPooledThread(@NotNull final Consumer<ProgressIndicator> task, @NotNull Disposable parent, final ModalityState modalityState) { final ProgressIndicator indicator = new EmptyProgressIndicator() { @NotNull @Override public ModalityState getModalityState() { return modalityState; } }; final Disposable disposable = new Disposable() { @Override public void dispose() { if (indicator.isRunning()) indicator.cancel(); } }; Disposer.register(parent, disposable); indicator.start(); ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { @Override public void run() { ProgressManager.getInstance().executeProcessUnderProgress(new Runnable() { @Override public void run() { try { task.consume(indicator); } finally { indicator.stop(); Disposer.dispose(disposable); } } }, indicator); } }); return indicator; } }