Skip to content

Commit e93a2a0

Browse files
committed
Make some modifications to the wait functions in the cllojure.clr.async.task API
1 parent de30802 commit e93a2a0

File tree

1 file changed

+107
-60
lines changed
  • Clojure/Clojure.Source/clojure/clr/async

1 file changed

+107
-60
lines changed

Clojure/Clojure.Source/clojure/clr/async/task.clj

Lines changed: 107 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,25 @@
44
(:refer-clojure :exclude [await])
55
(:import [System.Threading.Tasks Task]))
66

7+
(alias-type TaskObj |System.Threading.Tasks.Task`1[Object]|)
78

8-
;; ── Internal helpers ──────────────────────────────────────────────────
9+
10+
;; ── Extracting a Task result ──────────────────────
911

1012
; This would get a reflection warning, so we don't turn on *warn-on-reflection* until after.
1113
; We WANT a reflection warning -- we need the DLR callsite mechanism on the call to .GetAwaiter.
1214
; If we tag the task parameter as ^Task, we always pick up Task.GetAwaiter(), even for generic Task<TResult> tasks.
1315
; That just does not work.
1416

15-
(defn- task-result
16-
"Extracts the result from a completed Task<T> via its typed awaiter.
17-
Uses GetAwaiter().GetResult() which unwraps exceptions cleanly
18-
(no AggregateException wrapping unlike .Result).
19-
Returns nil for non-generic Task (void)."
17+
(defn result
18+
"Blocks the calling thread until the task completes and returns its result.
19+
For Task<T>, returns T. For void Task (non-generic), returns nil.
20+
Unwraps AggregateException to throw the inner exception directly.
21+
22+
Usage:
23+
(t/result (t/->task 42)) ;=> 42
24+
(t/result (t/completed-task)) ;=> nil
25+
(t/result (t/async (t/await (t/delay-task 100)) \"done\")) ;=> \"done\""
2026
[task]
2127
(let [t (.GetType ^Object task)]
2228
(when (.IsGenericType t)
@@ -52,41 +58,101 @@
5258
[& body]
5359
`((^:async fn* [] ~@body)))
5460

55-
;; ── Functions ─────────────────────────────────────────────────────────
56-
57-
(def ^:private task-object-type |System.Threading.Tasks.Task`1[System.Object]|)
58-
59-
(defn ^:async await-all
60-
"Awaits all tasks in parallel. Takes a collection of any Task types
61-
(Task<string>, Task<int>, Task<Object>, etc.).
62-
Returns an Object[] of results. Must be called in an async context.
63-
64-
Usage:
65-
(let [results (t/await (t/await-all [task-a task-b task-c]))]
66-
(vec results))"
67-
[tasks]
68-
(let [^|System.Threading.Tasks.Task[]| task-array (into-array Task tasks)]
69-
(if (every? #(instance? task-object-type %) task-array)
70-
;; Fast path: all Task<Object> — use generic WhenAll, no per-element reflection
71-
(let [^|System.Threading.Tasks.Task`1[System.Object][]| obj-array
72-
(into-array task-object-type tasks)]
73-
(await* (Task/WhenAll (type-args Object) obj-array)))
74-
;; Slow path: mixed types — await non-generic, extract results via reflection
75-
(do
76-
(await* (Task/WhenAll task-array))
77-
(into-array Object (map task-result task-array))))))
78-
79-
(defn ^:async await-any
80-
"Awaits the first task to complete from a collection of any Task types.
81-
Returns the first completed Task (not its result).
82-
Must be called in an async context.
83-
84-
Usage:
85-
(let [winner (t/await (t/await-any [fast-task slow-task]))]
86-
(t/result winner))"
87-
[tasks]
88-
(let [^|System.Threading.Tasks.Task[]| task-array (into-array Task tasks)]
89-
(await* (Task/WhenAny task-array))))
61+
;; ── Waiting, but staying non-:async ─────────────────────────────────────────────────────────
62+
63+
(defn- convert-timeout
64+
"Converts a Clojure timeout value to an int for Task.WaitAll.
65+
Accepts nil (no timeout), a number (milliseconds), or a TimeSpan."
66+
[timeout]
67+
(cond
68+
(nil? timeout) -1
69+
(number? timeout) (int timeout)
70+
:else (int (.TotalMilliseconds ^TimeSpan timeout))))
71+
72+
(defn wait-all
73+
"Waits for all of the provided Tasks to complete execution. Returns nil.
74+
75+
Usage: (t/wait-all tasks)
76+
(t/wait-all tasks timeout)
77+
(t/wait-all task timeout cancellation-token)
78+
79+
timeout values can be any numeric value (cast to int, milliseconds, -1 = no limit) or a TimeSpan"
80+
([tasks] (Task/WaitAll ^Task/1 (into-array Task tasks)))
81+
([tasks timeout] (Task/WaitAll ^Task/1 (into-array Task tasks) ^int (convert-timeout timeout)))
82+
([tasks timeout cancellation-token] (Task/WaitAll ^Task/1 (into-array Task tasks) (convert-timeout timeout) cancellation-token)))
83+
84+
85+
(defn wait-any
86+
"Waits for any of the provided Task to complete execution. Returns the task that completed or nil if a timeout occurred.
87+
88+
Usage: (t/wait-any tasks)
89+
(t/wait-any tasks timeout)
90+
(t/wait-any task timeout cancellation-token)
91+
92+
timeout values can be any numeric value (cast to int, milliseconds, -1 = no limit) or a TimeSpan"
93+
([tasks]
94+
(let [^Task/1 task-array (into-array Task tasks)
95+
idx (Task/WaitAny task-array)]
96+
(nth tasks idx)))
97+
([tasks timeout]
98+
(let [^Task/1 task-array (into-array Task tasks)
99+
idx (Task/WaitAny task-array ^int (convert-timeout timeout))]
100+
(when-not (= idx -1)
101+
(nth tasks idx))))
102+
([tasks timeout cancellation-token]
103+
(let [^Task/1 task-array (into-array Task tasks)
104+
idx (Task/WaitAny task-array (convert-timeout timeout) cancellation-token)]
105+
(when-not (= idx -1)
106+
(nth tasks idx)))))
107+
108+
(defn wait-all-results
109+
"Waits for all of the provided Tasks to complete execution. Returns a lazy sequence of the result for each task, with nil with for non-generic Tasks.
110+
111+
Usage: (t/wait-all-results tasks)
112+
(t/wait-all-results tasks timeout)
113+
(t/wait-all-results task timeout cancellation-token)
114+
115+
timeout values can be any numeric value (cast to int, milliseconds, -1 = no limit) or a TimeSpan"
116+
117+
([tasks]
118+
(let [^Task/1 task-array (into-array Task tasks)]
119+
(Task/WaitAll task-array)
120+
(map result tasks)))
121+
([tasks timeout]
122+
(let [^Task/1 task-array (into-array Task tasks)]
123+
(Task/WaitAll task-array ^int (convert-timeout timeout))
124+
(map result tasks)))
125+
([tasks timeout cancellation-token]
126+
(let [^Task/1 task-array (into-array Task tasks)]
127+
(Task/WaitAll task-array (convert-timeout timeout) cancellation-token)
128+
(map result tasks))))
129+
130+
(defn wait-any-result
131+
"Waits for any of the provided Task to complete execution. Returns the result of the task that completed or nil if a timeout occurred.
132+
133+
Usage: (t/wait-any-results tasks)
134+
(t/wait-any-results tasks timeout)
135+
(t/wait-any-results task timeout cancellation-token)
136+
137+
timeout values can be any numeric value (cast to int, milliseconds, -1 = no limit) or a TimeSpan"
138+
([tasks]
139+
(let [^Task/1 task-array (into-array Task tasks)
140+
idx (Task/WaitAny task-array)]
141+
(when-not (= idx -1)
142+
(result (nth tasks idx)))))
143+
([tasks timeout]
144+
(let [^Task/1 task-array (into-array Task tasks)
145+
idx (Task/WaitAny task-array ^int (convert-timeout timeout))]
146+
(when-not (= idx -1)
147+
(result (nth tasks idx)))))
148+
([tasks timeout cancellation-token]
149+
(let [^Task/1 task-array (into-array Task tasks)
150+
idx (Task/WaitAny task-array (convert-timeout timeout) cancellation-token)]
151+
(when-not (= idx -1)
152+
(result (nth tasks idx))))))
153+
154+
155+
;; ── Creating standard tasks
90156

91157
(defn delay-task
92158
"Returns a Task that completes after the specified duration in milliseconds.
@@ -97,15 +163,6 @@
97163
[milliseconds]
98164
(Task/Delay (int milliseconds)))
99165

100-
(defn run
101-
"Runs f (zero-arg fn) on the thread pool. Returns Task<object>.
102-
103-
Usage:
104-
(t/await (t/run (fn [] (+ 1 2 3))))"
105-
[f]
106-
(let [func (gen-delegate |System.Func`1[System.Object]| [] (f))]
107-
(Task/Run func)))
108-
109166
(defn ->task
110167
"Wraps a value in a completed Task<object>.
111168
@@ -124,14 +181,4 @@
124181
[x]
125182
(instance? Task x))
126183

127-
(defn result
128-
"Blocks the calling thread until the task completes and returns its result.
129-
For Task<T>, returns T. For void Task, returns nil.
130-
Unwraps AggregateException to throw the inner exception directly.
131184

132-
Usage:
133-
(t/result (t/->task 42)) ;=> 42
134-
(t/result (t/completed-task)) ;=> nil
135-
(t/result (t/async (t/await (t/delay-task 100)) \"done\")) ;=> \"done\""
136-
[task]
137-
(task-result task))

0 commit comments

Comments
 (0)