Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: edu/emory/mathcs/util/concurrent/AsyncTask.java


1   /* ***** BEGIN LICENSE BLOCK *****
2    * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3    *
4    * The contents of this file are subject to the Mozilla Public License Version
5    * 1.1 (the "License"); you may not use this file except in compliance with
6    * the License. You may obtain a copy of the License at
7    * http://www.mozilla.org/MPL/
8    *
9    * Software distributed under the License is distributed on an "AS IS" basis,
10   * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11   * for the specific language governing rights and limitations under the
12   * License.
13   *
14   * The Original Code is the Emory Utilities.
15   *
16   * The Initial Developer of the Original Code is
17   * The Distributed Computing Laboratory, Emory University.
18   * Portions created by the Initial Developer are Copyright (C) 2002
19   * the Initial Developer. All Rights Reserved.
20   *
21   * Alternatively, the contents of this file may be used under the terms of
22   * either the GNU General Public License Version 2 or later (the "GPL"), or
23   * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
24   * in which case the provisions of the GPL or the LGPL are applicable instead
25   * of those above. If you wish to allow use of your version of this file only
26   * under the terms of either the GPL or the LGPL, and not to allow others to
27   * use your version of this file under the terms of the MPL, indicate your
28   * decision by deleting the provisions above and replace them with the notice
29   * and other provisions required by the GPL or the LGPL. If you do not delete
30   * the provisions above, a recipient may use your version of this file under
31   * the terms of any one of the MPL, the GPL or the LGPL.
32   *
33   * ***** END LICENSE BLOCK ***** */
34  
35  package edu.emory.mathcs.util.concurrent;
36  
37  import edu.emory.mathcs.util.concurrent.*;
38  
39  /**
40   * A  class maintaining a single reference variable serving as the result
41   * of an operation. The result cannot be accessed until it has been set.
42   * <p>
43   * This class is intended primarily for subclassing. Typical usage scenario
44   * is to create a new instance and invoke
45   * {@link #createPerformer createPerformer}, thus
46   * obtaining runnable that will execute specified task and set results in
47   * that instance. Note that such obtained runnable should be executed only
48   * once -- subsequent execution attempts will fail due to "task already
49   * completed" condition.
50   *
51   * @see Executor
52   **/
53  public class AsyncTask implements Future, Cancellable {
54  
55      /** completion status */
56      volatile boolean completed = false;
57  
58      CancellationException cancellationException;
59  
60      /**
61       * Wrapper for the thread in which async task is executed, or the task
62       * itself if it implements {@link Cancellable}. Set by the thread that
63       * is just about to start executing the task.
64       */
65      protected Cancellable cancellationHandler;
66  
67      /** Optional callback to invoke when task completes */
68      final Callback callback;
69  
70      /** result of a task */
71      Object result;
72  
73      /** exception object in case task completed abruptly */
74      Throwable resultException;
75  
76      protected AsyncTask() { this(null); }
77  
78      protected AsyncTask(Callback cb) {
79          this.callback = cb;
80      }
81  
82      /**
83       * Checks if the task has completed. Not synchronized to improve
84       * concurrency, using "volatile" instead
85       * @return <tt>true</tt> if task completed, <tt>false</tt> otherwise.
86       */
87      public boolean isDone() {
88          return completed;
89      }
90  
91      public boolean cancel(boolean mayInterruptIfRunning) {
92          Throwable resultException = null;
93          synchronized (this) {
94              if (cancellationHandler != null) {
95                 // task is currently running
96                 if (mayInterruptIfRunning) {
97                     return cancellationHandler.cancel(true);
98                 }
99                 else {
100                    return false;
101                }
102             }
103             else if (!completed) {
104                 // task not yet started. We could set this as completed
105                 // abruptly at this point, but then we would have to call
106                 // the callback, which would execute within the current thread,
107                 // possibly blocking. It seems safer to guarantee that
108                 // "interrupt()" always return promptly, and the interruption
109                 // status is propagated by the same thread which would execute
110                 // the task.
111                 this.cancellationException = new CancellationException("task cancelled");
112                 return true;
113             }
114             else {
115                 // task already completed
116                 return false;
117             }
118         }
119     }
120 
121     public synchronized boolean isCancelled() {
122         if (cancellationException != null) return true;
123         if (cancellationHandler != null) return cancellationHandler.isCancelled();
124         return false;
125     }
126 
127     /**
128      * Marks the task as completed.
129      * @param result the result of a task.
130      */
131     protected void setCompleted(Object result) {
132         synchronized (this) {
133             if (completed) {
134                 throw new IllegalStateException("task already completed");
135             }
136             this.completed = true;
137             this.result = result;
138             this.cancellationHandler = null;
139             notifyAll();
140         }
141 
142         // invoking callbacks *after* setting future as completed and
143         // outside the synchronization block makes it safe to call
144         // interrupt() from within callback code (in which case it will be
145         // ignored rather than cause deadlock / illegal state exception)
146         invokeCallback(result, null);
147     }
148 
149     /**
150      * Marks the task as failed.
151      * @param exception the cause of abrupt completion.
152      * @throws IllegalStateException if task had been completed already.
153      */
154     protected void setFailed(Throwable exception) {
155         synchronized (this) {
156             if (completed) {
157                 throw new IllegalStateException("task already completed");
158             }
159             this.completed = true;
160             this.resultException = exception;
161             this.cancellationHandler = null;
162             notifyAll();
163         }
164 
165         // invoking callbacks *after* setting future as completed and
166         // outside the synchronization block makes it safe to call
167         // interrupt() from within callback code (in which case it will be
168         // ignored rather than cause deadlock / illegal state exception)
169         invokeCallback(null, exception);
170     }
171 
172     public synchronized Object get()
173         throws InterruptedException, ExecutionException
174     {
175         waitFor();
176         return getResult();
177     }
178 
179     public synchronized Object get(long timeout, TimeUnit tunit)
180         throws InterruptedException, ExecutionException, TimeoutException
181     {
182         waitFor(tunit.convert(timeout, TimeUnit.MILLISECONDS));
183         if (!completed) {
184             throw new TimeoutException("Timeout when waiting for a result");
185         }
186         return getResult();
187     }
188 
189     /**
190      * Waits for the task to complete.
191      */
192     private void waitFor() throws InterruptedException {
193         while (!completed) {
194             wait();
195         }
196     }
197 
198     /**
199      * Waits for the task to complete for timeout milliseconds.
200      */
201     private void waitFor(long timeout) throws InterruptedException {
202         while (!completed && timeout > 0) {
203             long timestart = System.currentTimeMillis();
204             wait(timeout);
205             if (completed) return;
206             timeout -= (System.currentTimeMillis() - timestart);
207         }
208     }
209 
210     /**
211      * Gets the result of the task.
212      *
213      * PRE: task completed
214      * PRE: called from synchronized block
215      */
216     private Object getResult() throws ExecutionException {
217         if (resultException != null) {
218             throw new ExecutionException("task completed abruptly", resultException);
219         }
220         return result;
221     }
222 
223     /**
224      * Schedules specified task with given executor, and returns
225      * completion handle that can be used to access the result or to cancel
226      * the task. Later, if task completes successfully, the handle is marked
227      * completed with the result that has been returned from the task.
228      * If the task ends with an exception, the handle is marked
229      * failed with cause being that exception. In such case, as a debugging
230      * aid, current stack trace (i.e. that of this method's invoker) is
231      * appended to the original stack trace.
232      *
233      * @param executor the executor to use
234      * @param call the task to schedule
235      * @return completion handle that can be used to access the result or
236      *         cancel the task.
237      */
238     public static AsyncTask start(final Executor executor, final Callable call)
239     {
240         return start(executor, call, null);
241     }
242 
243     /**
244      * Schedules specified task with given executor, and returns
245      * completion handle that can be used to access the result or to cancel
246      * the task. Later, if task completes successfully, the handle is marked
247      * completed with the result that has been returned from the task.
248      * If the task ends with an exception, the handle is marked
249      * failed with cause being that exception. In such case, as a debugging
250      * aid, current stack trace (i.e. that of this method's invoker) is
251      * appended to the original stack trace.
252      *
253      * @param executor the executor to use
254      * @param call the task to schedule
255      * @param cb callback to invoke upon completion
256      * @return completion handle that can be used to access the result or
257      *         cancel the task.
258      */
259     public static AsyncTask start(final Executor executor, final Callable call,
260                                   final Callback cb)
261     {
262         return start(executor, call, cb, false);
263     }
264 
265     /**
266      * Schedules specified task with given executor, and returns
267      * completion handle that can be used to access the result or to cancel
268      * the task. Later, if task completes successfully, the handle is marked
269      * completed with the result that has been returned from the task.
270      * If the task ends with an exception, the handle is marked
271      * failed with cause being that exception. In such case, as a debugging
272      * aid, current stack trace (i.e. that of this method's invoker) is
273      * appended to the original stack trace unless
274      * the disableStackTraces parameter is set to false
275      *
276      * @param executor the executor to use
277      * @param call the task to schedule
278      * @param cb callback to invoke upon completion
279      * @param disableStackTraces if true, does not append invoker stack trace
280      *        to traces of exceptions thrown during task execution
281      * @return completion handle that can be used to access the result or
282      *         cancel the task.
283      */
284     public static AsyncTask start(final Executor executor, final Callable call,
285                                   final Callback cb, boolean disableStackTraces)
286     {
287         final AsyncTask task = new AsyncTask(cb);
288         executor.execute(task.createPerformer(call, disableStackTraces));
289         return task;
290     }
291 
292     /**
293      * Creates a runnable that will execute specified call and then mark this
294      * AsyncTask with the result of that call. If the call completes
295      * successfully, this AsyncTask is marked
296      * completed with the result returned by the call. If the call
297      * throws an exception, the AsyncTask is marked as failed with
298      * cause being that exception. The stack trace of the thread in which
299      * the performer is created is appended to the failure cause stack
300      * trace unless the disableStackTraces parameter is set to false.
301      * <p>
302      * This method is intended to be used by subclasses. Runnable
303      * returned from this method should be executed only once -- subsequent
304      * execution attempts will fail due to "task already completed" condition.
305      *
306      * @param call the call to execute
307      * @param disableStackTraces if true, does not append invoker stack trace
308      *        to traces of exceptions thrown during execution of the runnable
309      * @return runnable that will execute specified call and
310      *         set the result of this AsyncTask upon completion
311      */
312     protected Runnable createPerformer(final Callable call,
313                                        boolean disableStackTraces)
314     {
315         final Throwable stackCxt;
316         if (disableStackTraces) {
317             stackCxt = null;
318         }
319         else {
320             stackCxt = new Throwable();
321         }
322 
323         return new Runnable() {
324             public void run() {
325                 try {
326                     synchronized (this) {
327                         if (cancellationException != null) {
328                             // async interruption before task started; propagate
329                             throw cancellationException;
330                         }
331                         else {
332                             cancellationHandler = createCancellationHandler(call);
333                         }
334                     }
335 
336                     Object result = call.call();
337                     synchronized (this) {
338                         cancellationHandler = null;
339                         if (Thread.interrupted()) {
340                             throw new InterruptedException("Task interrupted");
341                         }
342                     }
343                     setCompleted(result);
344                 }
345                 catch (Throwable ex) {
346                     if (stackCxt != null) {
347                         appendContextStackTrace(ex, stackCxt);
348                     }
349                     setFailed(ex);
350                 }
351             }
352         };
353     }
354 
355     /**
356      * Overridable cancellation policy that governs what should be done upon
357      * cancellation of tasks that have already started running.
358      * This method is invoked in the worker thread by the runnable created
359      * by {@link #createPerformer createPerformer}, before invoking the actual
360      * call. The
361      * cancellationHandler returned from this method is then stored in this
362      * AsyncTask. Later, if the user attempts cancellation while the call is
363      * already executing, the request is delegated to the handler which must
364      * then supply the appropriate action and indication of success or failure.
365      * <p>
366      * The default implementation behaves as follows. If the callable for
367      * which the cancellation handler is requested implements
368      * {@link Cancellable} itself, that callable itself is returned as its own
369      * cancellation handler; in other words, the cancellation policy will be
370      * supplied directly by the callable implementation. Otherwise, the
371      * default behavior is to interrupt the worker thread if the
372      * mayInterruptIfRunning parameter is set to true, and fail in the other
373      * case.
374      *
375      * @param call the call for which the cancellation handler is requested
376      * @return cancellation handler that handles cancellation attempts
377      *         while the task is already executing
378      */
379     protected Cancellable createCancellationHandler(Callable call) {
380         if (call instanceof Cancellable) {
381             return (Cancellable) call;
382         }
383         else {
384             final Thread workThread = Thread.currentThread();
385             return new Cancellable() {
386                 public boolean cancel(boolean mayInterruptIfRunning) {
387                     if (mayInterruptIfRunning) {
388                         workThread.interrupt();
389                         return true;
390                     }
391                     return false;
392                 }
393                 public boolean isDone() {
394                     return false;
395                 }
396                 public boolean isCancelled() {
397                     return workThread.isInterrupted();
398                 }
399             };
400         }
401     }
402 
403     private static void appendContextStackTrace(Throwable ex, Throwable cxt) {
404         StackTraceElement[] exTrace = ex.getStackTrace();
405         StackTraceElement[] cxtTrace = cxt.getStackTrace();
406         StackTraceElement[] combinedTrace =
407             new StackTraceElement[exTrace.length + cxtTrace.length];
408         System.arraycopy(exTrace, 0, combinedTrace, 0,
409                          exTrace.length);
410         System.arraycopy(cxtTrace, 0, combinedTrace, exTrace.length, cxtTrace.length);
411         ex.setStackTrace(combinedTrace);
412     }
413 
414     private void invokeCallback(Object result, Throwable resultException) {
415         if (callback != null) {
416             try {
417                 if (resultException != null) {
418                     callback.failed(resultException);
419                 } else {
420                     callback.completed(result);
421                 }
422             }
423             catch (Throwable error) {
424                 // cannot propagate; caller of e.g. setCompleted() is not
425                 // interested in the exception from callback
426                 java.io.PrintStream s = System.err;
427                 synchronized (s) {
428                     s.print("Exception occured within callback: ");
429                     error.printStackTrace(s);
430                 }
431             }
432         }
433     }
434 }