diff --git a/src/main/java/com/aparapi/Execution.java b/src/main/java/com/aparapi/Execution.java
new file mode 100644
index 00000000..0966cab0
--- /dev/null
+++ b/src/main/java/com/aparapi/Execution.java
@@ -0,0 +1,86 @@
+/**
+ * Copyright (c) 2016 - 2018 Syncleus, Inc.
+ *
+ * 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.aparapi;
+
+/**
+ * Handle returned by asynchronous kernel execution.
+ */
+public final class Execution {
+ private final Kernel kernel;
+ private final Thread thread;
+ private volatile Throwable failure;
+ private volatile boolean finished;
+
+ Execution(final Kernel _kernel, final Runnable _execution) {
+ if (_kernel == null) {
+ throw new NullPointerException("_kernel");
+ }
+ if (_execution == null) {
+ throw new NullPointerException("_execution");
+ }
+ kernel = _kernel;
+ thread = new Thread(new Runnable() {
+ @Override public void run() {
+ try {
+ _execution.run();
+ } catch (Throwable t) {
+ failure = t;
+ } finally {
+ finished = true;
+ }
+ }
+ }, "Aparapi Kernel Async Execution");
+ thread.start();
+ }
+
+ /**
+ * Wait until the asynchronous execution has finished.
+ *
+ * @return the executed kernel
+ */
+ public Kernel waitUntilFinished() {
+ boolean interrupted = false;
+ while (true) {
+ try {
+ thread.join();
+ break;
+ } catch (InterruptedException e) {
+ interrupted = true;
+ }
+ }
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ if (failure instanceof RuntimeException) {
+ throw (RuntimeException) failure;
+ }
+ if (failure instanceof Error) {
+ throw (Error) failure;
+ }
+ if (failure != null) {
+ throw new RuntimeException(failure);
+ }
+ return kernel;
+ }
+
+ public boolean isFinished() {
+ return finished;
+ }
+
+ public Kernel getKernel() {
+ return kernel;
+ }
+}
diff --git a/src/main/java/com/aparapi/Kernel.java b/src/main/java/com/aparapi/Kernel.java
index 4b9686db..03700190 100644
--- a/src/main/java/com/aparapi/Kernel.java
+++ b/src/main/java/com/aparapi/Kernel.java
@@ -2907,6 +2907,75 @@ public synchronized Kernel execute(String _entrypoint, Range _range, int _passes
return prepareKernelRunner().execute(_entrypoint, _range, _passes);
}
+ /**
+ * Start execution of _range kernels asynchronously.
+ *
+ * @param _range The range of Kernels that we would like to initiate.
+ * @return an execution handle which can be used to wait for completion
+ */
+ public Execution executeAsync(final Range _range) {
+ return executeAsync(_range, 1);
+ }
+
+ /**
+ * Start execution of _range kernels asynchronously.
+ *
+ * @param _range The number of Kernels that we would like to initiate.
+ * @return an execution handle which can be used to wait for completion
+ */
+ public Execution executeAsync(final int _range) {
+ return executeAsync(createRange(_range), 1);
+ }
+
+ /**
+ * Start execution of _passes iterations of _range kernels asynchronously.
+ *
+ * @param _range The range of Kernels that we would like to initiate.
+ * @param _passes The number of passes to make.
+ * @return an execution handle which can be used to wait for completion
+ */
+ public Execution executeAsync(final Range _range, final int _passes) {
+ return executeAsync("run", _range, _passes);
+ }
+
+ /**
+ * Start execution of _passes iterations over the _range of kernels asynchronously.
+ *
+ * @param _range The number of Kernels that we would like to initiate.
+ * @param _passes The number of passes to make.
+ * @return an execution handle which can be used to wait for completion
+ */
+ public Execution executeAsync(final int _range, final int _passes) {
+ return executeAsync(createRange(_range), _passes);
+ }
+
+ /**
+ * Start execution of _range kernels for the given entrypoint asynchronously.
+ *
+ * @param _entrypoint is the name of the method we wish to use as the entrypoint to the kernel
+ * @param _range The range of Kernels that we would like to initiate.
+ * @return an execution handle which can be used to wait for completion
+ */
+ public Execution executeAsync(final String _entrypoint, final Range _range) {
+ return executeAsync(_entrypoint, _range, 1);
+ }
+
+ /**
+ * Start execution of _passes iterations for the given entrypoint asynchronously.
+ *
+ * @param _entrypoint is the name of the method we wish to use as the entrypoint to the kernel
+ * @param _range The range of Kernels that we would like to initiate.
+ * @param _passes The number of passes to make.
+ * @return an execution handle which can be used to wait for completion
+ */
+ public Execution executeAsync(final String _entrypoint, final Range _range, final int _passes) {
+ return new Execution(this, new Runnable() {
+ @Override public void run() {
+ execute(_entrypoint, _range, _passes);
+ }
+ });
+ }
+
/**
* Force pre-compilation of the kernel for a given device, without executing it.
*
diff --git a/src/test/java/com/aparapi/runtime/KernelAsyncExecutionTest.java b/src/test/java/com/aparapi/runtime/KernelAsyncExecutionTest.java
new file mode 100644
index 00000000..772b3003
--- /dev/null
+++ b/src/test/java/com/aparapi/runtime/KernelAsyncExecutionTest.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (c) 2016 - 2018 Syncleus, Inc.
+ *
+ * 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.aparapi.runtime;
+
+import com.aparapi.Execution;
+import com.aparapi.Kernel;
+import com.aparapi.Range;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class KernelAsyncExecutionTest {
+
+ @Test(timeout = 5000)
+ public void executeAsyncRangeRunsInBackgroundAndCanBeWaitedOn() throws Exception {
+ final int[] output = new int[1];
+ final CountDownLatch enteredKernel = new CountDownLatch(1);
+ final CountDownLatch releaseKernel = new CountDownLatch(1);
+ Kernel kernel = new Kernel() {
+ @Override
+ public void run() {
+ enteredKernel.countDown();
+ try {
+ releaseKernel.await(2, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new RuntimeException(e);
+ }
+ output[getGlobalId()] = getGlobalId() + 1;
+ }
+ };
+ kernel.setExecutionMode(Kernel.EXECUTION_MODE.JTP);
+
+ Execution execution = kernel.executeAsync(Range.create(1));
+
+ Assert.assertTrue(enteredKernel.await(2, TimeUnit.SECONDS));
+ Assert.assertFalse(execution.isFinished());
+ releaseKernel.countDown();
+ Assert.assertSame(kernel, execution.waitUntilFinished());
+ Assert.assertTrue(execution.isFinished());
+ Assert.assertEquals(1, output[0]);
+ }
+}