|
17 | 17 |
|
18 | 18 | import java.util.PriorityQueue;
|
19 | 19 | import java.util.concurrent.TimeUnit;
|
20 |
| -import java.util.concurrent.atomic.AtomicInteger; |
| 20 | +import java.util.concurrent.atomic.AtomicLong; |
21 | 21 |
|
22 | 22 | import rx.Scheduler;
|
23 | 23 | import rx.Subscription;
|
| 24 | +import rx.subscriptions.MultipleAssignmentSubscription; |
| 25 | +import rx.util.functions.Func1; |
24 | 26 | import rx.util.functions.Func2;
|
25 | 27 |
|
26 | 28 | /**
|
27 | 29 | * Schedules work on the current thread but does not execute immediately. Work is put in a queue and executed after the current unit of work is completed.
|
28 | 30 | */
|
29 | 31 | public class CurrentThreadScheduler extends Scheduler {
|
30 | 32 | private static final CurrentThreadScheduler INSTANCE = new CurrentThreadScheduler();
|
| 33 | + private static final AtomicLong counter = new AtomicLong(0); |
31 | 34 |
|
32 | 35 | public static CurrentThreadScheduler getInstance() {
|
33 | 36 | return INSTANCE;
|
34 | 37 | }
|
35 | 38 |
|
36 |
| - private static final ThreadLocal<PriorityQueue<TimedAction>> QUEUE = new ThreadLocal<PriorityQueue<TimedAction>>(); |
| 39 | + private static final ThreadLocal<PriorityQueue<TimedAction>> QUEUE = new ThreadLocal<PriorityQueue<TimedAction>>() { |
| 40 | + protected java.util.PriorityQueue<TimedAction> initialValue() { |
| 41 | + return new PriorityQueue<TimedAction>(); |
| 42 | + }; |
| 43 | + }; |
| 44 | + |
| 45 | + private static final ThreadLocal<Boolean> PROCESSING = new ThreadLocal<Boolean>() { |
| 46 | + protected Boolean initialValue() { |
| 47 | + return Boolean.FALSE; |
| 48 | + }; |
| 49 | + }; |
37 | 50 |
|
38 | 51 | /* package accessible for unit tests */CurrentThreadScheduler() {
|
39 | 52 | }
|
40 | 53 |
|
41 |
| - private final AtomicInteger counter = new AtomicInteger(0); |
42 |
| - |
43 | 54 | @Override
|
44 | 55 | public <T> Subscription schedule(T state, Func2<? super Scheduler, ? super T, ? extends Subscription> action) {
|
45 |
| - DiscardableAction<T> discardableAction = new DiscardableAction<T>(state, action); |
46 |
| - enqueue(discardableAction, now()); |
47 |
| - return discardableAction; |
| 56 | + // immediately move to the InnerCurrentThreadScheduler |
| 57 | + InnerCurrentThreadScheduler innerScheduler = new InnerCurrentThreadScheduler(); |
| 58 | + innerScheduler.schedule(state, action); |
| 59 | + enqueueFromOuter(innerScheduler, now()); |
| 60 | + return innerScheduler; |
48 | 61 | }
|
49 | 62 |
|
50 | 63 | @Override
|
51 |
| - public <T> Subscription schedule(T state, Func2<? super Scheduler, ? super T, ? extends Subscription> action, long dueTime, TimeUnit unit) { |
52 |
| - long execTime = now() + unit.toMillis(dueTime); |
53 |
| - |
54 |
| - DiscardableAction<T> discardableAction = new DiscardableAction<T>(state, new SleepingAction<T>(action, this, execTime)); |
55 |
| - enqueue(discardableAction, execTime); |
56 |
| - return discardableAction; |
| 64 | + public <T> Subscription schedule(T state, Func2<? super Scheduler, ? super T, ? extends Subscription> action, long delayTime, TimeUnit unit) { |
| 65 | + long execTime = now() + unit.toMillis(delayTime); |
| 66 | + |
| 67 | + // create an inner scheduler and queue it for execution |
| 68 | + InnerCurrentThreadScheduler innerScheduler = new InnerCurrentThreadScheduler(); |
| 69 | + innerScheduler.schedule(state, action, delayTime, unit); |
| 70 | + enqueueFromOuter(innerScheduler, execTime); |
| 71 | + return innerScheduler; |
57 | 72 | }
|
58 | 73 |
|
59 |
| - private void enqueue(DiscardableAction<?> action, long execTime) { |
| 74 | + /* |
| 75 | + * This will accept InnerCurrentThreadScheduler instances and execute them in order they are received |
| 76 | + * and on each of them will loop internally until each is complete. |
| 77 | + */ |
| 78 | + private void enqueueFromOuter(final InnerCurrentThreadScheduler innerScheduler, long execTime) { |
| 79 | + // Note that everything here is single-threaded so we won't have race conditions |
60 | 80 | PriorityQueue<TimedAction> queue = QUEUE.get();
|
61 |
| - boolean exec = queue == null; |
| 81 | + queue.add(new TimedAction(new Func1<Scheduler, Subscription>() { |
62 | 82 |
|
63 |
| - if (exec) { |
64 |
| - queue = new PriorityQueue<TimedAction>(); |
65 |
| - QUEUE.set(queue); |
| 83 | + @Override |
| 84 | + public Subscription call(Scheduler _) { |
| 85 | + // when the InnerCurrentThreadScheduler gets scheduled we want to process its tasks |
| 86 | + return innerScheduler.startProcessing(); |
| 87 | + } |
| 88 | + }, execTime, counter.incrementAndGet())); |
| 89 | + |
| 90 | + // first time through starts the loop |
| 91 | + if (!PROCESSING.get()) { |
| 92 | + PROCESSING.set(Boolean.TRUE); |
| 93 | + while (!queue.isEmpty()) { |
| 94 | + queue.poll().action.call(innerScheduler); |
| 95 | + } |
| 96 | + PROCESSING.set(Boolean.FALSE); |
66 | 97 | }
|
| 98 | + } |
67 | 99 |
|
68 |
| - queue.add(new TimedAction(action, execTime, counter.incrementAndGet())); |
| 100 | + private static class InnerCurrentThreadScheduler extends Scheduler implements Subscription { |
| 101 | + private final MultipleAssignmentSubscription childSubscription = new MultipleAssignmentSubscription(); |
| 102 | + private final PriorityQueue<TimedAction> innerQueue = new PriorityQueue<TimedAction>(); |
69 | 103 |
|
70 |
| - if (exec) { |
71 |
| - while (!queue.isEmpty()) { |
72 |
| - queue.poll().action.call(this); |
| 104 | + @Override |
| 105 | + public <T> Subscription schedule(T state, Func2<? super Scheduler, ? super T, ? extends Subscription> action) { |
| 106 | + DiscardableAction<T> discardableAction = new DiscardableAction<T>(state, action); |
| 107 | + childSubscription.set(discardableAction); |
| 108 | + enqueue(discardableAction, now()); |
| 109 | + return childSubscription; |
| 110 | + } |
| 111 | + |
| 112 | + @Override |
| 113 | + public <T> Subscription schedule(T state, Func2<? super Scheduler, ? super T, ? extends Subscription> action, long delayTime, TimeUnit unit) { |
| 114 | + long execTime = now() + unit.toMillis(delayTime); |
| 115 | + |
| 116 | + DiscardableAction<T> discardableAction = new DiscardableAction<T>(state, new SleepingAction<T>(action, this, execTime)); |
| 117 | + childSubscription.set(discardableAction); |
| 118 | + enqueue(discardableAction, execTime); |
| 119 | + return childSubscription; |
| 120 | + } |
| 121 | + |
| 122 | + private void enqueue(Func1<Scheduler, Subscription> action, long execTime) { |
| 123 | + innerQueue.add(new TimedAction(action, execTime, counter.incrementAndGet())); |
| 124 | + } |
| 125 | + |
| 126 | + private Subscription startProcessing() { |
| 127 | + while (!innerQueue.isEmpty()) { |
| 128 | + innerQueue.poll().action.call(this); |
73 | 129 | }
|
| 130 | + return this; |
| 131 | + } |
74 | 132 |
|
75 |
| - QUEUE.set(null); |
| 133 | + @Override |
| 134 | + public void unsubscribe() { |
| 135 | + childSubscription.unsubscribe(); |
76 | 136 | }
|
| 137 | + |
77 | 138 | }
|
78 | 139 |
|
| 140 | + /** |
| 141 | + * Use time to sort items so delayed actions are sorted to their appropriate position in the queue. |
| 142 | + */ |
79 | 143 | private static class TimedAction implements Comparable<TimedAction> {
|
80 |
| - final DiscardableAction<?> action; |
| 144 | + final Func1<Scheduler, Subscription> action; |
81 | 145 | final Long execTime;
|
82 |
| - final Integer count; // In case if time between enqueueing took less than 1ms |
| 146 | + final Long count; // In case if time between enqueueing took less than 1ms |
83 | 147 |
|
84 |
| - private TimedAction(DiscardableAction<?> action, Long execTime, Integer count) { |
| 148 | + private TimedAction(Func1<Scheduler, Subscription> action, Long execTime, Long count) { |
85 | 149 | this.action = action;
|
86 | 150 | this.execTime = execTime;
|
87 | 151 | this.count = count;
|
|
0 commit comments