Dialogs await their Task through a generic #:async import

Bjolang now resolves a generic method imported #:async, so
Dispatcher.InvokeAsync(Func<Task<T>>) is imported as it is: it runs the
dialog on the UI thread and is awaited for what the dialog answers. The
detour through the non-generic Task.WhenAll and the Result read by
reflection are gone.

🤖 Generated with [ECA](https://eca.dev) (anthropic/claude-opus-4-8)

Co-Authored-By: eca-agent <git@eca.dev>
This commit is contained in:
Linus Björnstam 2026-10-25 18:36:53 +01:00
parent 8941425f05
commit f53877e7a1

View file

@ -29,9 +29,6 @@
(import/class
(Object (: System.Object))
(DispatcherOpBase (: Avalonia.Threading.DispatcherOperation))
(ClrType (: System.Type))
(PropertyInfo (: System.Reflection.PropertyInfo))
(ClrString (: System.String))
((ReadOnlyList %a) (: System.Collections.Generic.IReadOnlyList))
((EventHandler %a) (: System.EventHandler))
@ -77,20 +74,12 @@
;; The UI thread, and the window a dialog belongs to.
(ui-dispatcher (: Avalonia.Threading.Dispatcher.UIThread #:get))
(ui-post (: Avalonia.Threading.Dispatcher.InvokeAsync (-> Dispatcher (-> Unit) (DispatcherOp Unit))))
;; Runs a function on the UI thread.
;; Runs a function that starts a Task on the UI thread, and waits for the
;; Task. Avalonia's dialogs take no CancellationToken, so this cannot be
;; cancelled: a cancelled command stops waiting, and the dialog stays until
;; it is answered.
(on-ui (: Avalonia.Threading.Dispatcher.InvokeAsync
(-> Dispatcher (-> Unit) (DispatcherOp Unit))))
(op-task (: Avalonia.Threading.DispatcherOperation.GetTask (-> DispatcherOpBase Task)))
;; Waits for Tasks. Avalonia's dialogs take no CancellationToken, so this
;; cannot be cancelled: a cancelled command stops at its next sync, and the
;; dialog stays until it is answered.
(await-tasks (: System.Threading.Tasks.Task.WhenAll (-> (Array Task) void)
#:async #:uncancellable))
;; What a finished Task answers. By reflection, because Bjolang reads no
;; Task's Result: on a Task that is not finished, that would block.
(type-of (: System.Object.GetType (-> Object ClrType)))
(property-of (: System.Type.GetProperty (-> ClrType string PropertyInfo)))
(value-of (: System.Reflection.PropertyInfo.GetValue (-> PropertyInfo Object Object)))
(-> Dispatcher (-> (Task %a)) %a) #:async #:uncancellable))
(answered (: System.Threading.Tasks.Task.FromResult (-> %a (Task %a))))
(current-app (: Avalonia.Application.Current #:get))
(app-lifetime (: Avalonia.Application.ApplicationLifetime (-> Application ILifetime) #:get))
@ -171,16 +160,10 @@
;; Task it makes. `none` is the answer when there is no window.
(: (-bjo-> (-> Window (Task %a)) %a %a))
(defbjo (run-on-ui start none)
(def/mutable task (: (clr-default) Task))
(def op (on-ui ui-dispatcher
(fun ()
(set! task (cast Task (cast Object (match (owner)
(on-ui ui-dispatcher
(fun () (match (owner)
((Some w) (start w))
(None (answered none))))))
unit)))
(await-tasks #[(op-task (cast DispatcherOpBase (cast Object op)))])
(await-tasks #[task])
(cast %a (value-of (property-of (type-of (cast Object task)) "Result") (cast Object task))))
;; The event of what the Task that `start` makes answers, at each sync.
;; `none` is the answer when there is no window, or the Task fails.