class Task extends Object
Work that finishes later.
This class is exported by the KS module.
Every CLR call which returns a .NET Task hands one of these back, as does an async Task<T> member in a #CSharp block.
Await is how a script waits for one and reads its value.
TaskObj := Task(Value)
Wraps a CLR Task or ValueTask reached some other way, or a script object implementing __Await().
A RealThread raises a TypeError, because a worker has two completions. Pass worker.Task or worker.Terminated to say which is meant.
Value := TaskObj.Result
The value this task produced, or an empty string while it is still running, if it failed, or if it produced none.
Note: This is a snapshot and never waits. It is not the .NET Task.Result, which blocks. Await and Wait are the waiting forms.
Name := TaskObj.Status
The task's state as one of four strings:
| Value | Meaning |
|---|---|
"Pending" | The task has not reached a terminal outcome. |
"Succeeded" | It completed successfully; Result holds its value. |
"Failed" | It completed with an error; Error holds it. |
"Canceled" | It was canceled. Cancellation is not failure, so Error stays empty. |
True until the task reaches a terminal outcome.
True only after successful completion.
True only after completion with an error.
True only after cancellation.
Each predicate is a separate read, so two of them read in sequence can disagree if the task finishes in between; to act on one consistent state, read Status once, as in the switch below.
if t.IsFailed
MsgBox t.Error.Message
switch t.Status { ; one read, so the branches cannot disagree
case "Succeeded": MsgBox t.Result
case "Failed": MsgBox t.Error.Message
case "Canceled": MsgBox "canceled"
default: MsgBox "still running"
}
Err := TaskObj.Error
The failure as a catchable Error object once the task has failed, otherwise an empty string. Reading it counts as observing the failure, so it is not also reported as an uncaught error. It is the same Error object that Await throws for this task.
ClrTask := TaskObj.ToClr()
Returns the underlying CLR task as an ordinary Clr object, so members this class does not mirror — IsCompleted, Exception, ContinueWith — stay reachable. Unlike Then, its ContinueWith does not return to the calling thread or flatten results.
Warning: Waiting through this surface — ToClr().Result, ToClr().Wait(), ToClr().GetAwaiter().GetResult() — blocks the thread without checking messages, so work which still has to run on that same thread never gets the chance and the wait can last forever. Await and Wait pump while they wait.
Finished := TaskObj.Wait(Timeout)
Waits for the task, returning 1 (true) when it reaches any terminal outcome or 0 (false) only if Timeout elapses first. Failure and cancellation therefore return true; read Status or the Is* predicates for the outcome. Timeout is in milliseconds; omit it to wait indefinitely. Timing out stops only this wait and does not cancel the task.
Like Await, this pumps while it waits, so timers, hotkeys and the GUI stay responsive. Unlike Await it neither rethrows nor observes the task's failure; read Error afterwards.
A real thread cannot wait on its own entry function through RealThread.Task; doing so throws TargetError.
NextTask := TaskObj.Then(OnSuccess , OnFailure)
Runs OnSuccess after this task succeeds, or optional OnFailure after it fails, without blocking, and returns a Task for the rest of the chain.
OnSuccess(Value) OnFailure(Error)
OnSuccess receives the successful value, already unwrapped. OnFailure receives the same catchable Error exposed by Task.Error; returning normally recovers the chain. Either callback may declare no parameter. Requiring more than one raises ValueError when its target signature can be resolved. Without OnFailure, the same failure propagates to NextTask. Cancellation invokes neither callback and propagates unchanged.
The callback runs in its own script thread on the real thread where Then was called, so A_* variables, Critical and GUI access all behave normally. A pending callback keeps that owner alive; if the owner is shut down first, the returned Task fails.
If the selected callback returns a Task, CLR task or ValueTask, NextTask recursively adopts that work's value, failure or cancellation. A directly returned object implementing __Await() is adopted too. Once the callback has returned, the work it returned does not by itself keep the script running.
TaskObj := Task.WhenAll(Tasks*)
WhenAll finishes when every one of Tasks has finished, and its Result is an Array of their results in the order given. If any input fails, it fails; if at least one is canceled and none fail, it is canceled.
With no tasks, it finishes successfully with an empty Array.
TaskObj := Task.WhenAny(Tasks*)
Finishes as soon as the first of Tasks finishes and transfers that outcome: the winner's value, failure or cancellation. The losers keep running and are not observed or canceled. Calling it with no tasks raises a ValueError.
Both combinators accept an object implementing __Await() as well as a Task. A RealThread raises a TypeError.
TaskObj := Task.Create(Producer)
Creates and returns a Task controlled by Producer. The producer is called synchronously before Create returns and receives only the positional prefix it declares, up to three settlement functions. Its own return value is ignored.
Settled := Succeed(Value) ; settle from Value; adopt asynchronous work's eventual outcome Settled := Fail(Reason) ; finish with an Error or a descriptive string Settled := Cancel() ; finish as canceled
The first settlement wins and returns 1 (true); every later call returns 0 (false). An exception thrown by Producer before settlement fails the returned Task, while one thrown after settlement has no effect on it.
This is how a callback-shaped source of events — a hotkey, a GUI control, a device notification — takes part in a combinator or is handed to Await.
Within each script, exactly one Task object exists per underlying piece of work, so identity comparison answers whether two references describe the same work.
A task is canceled by its producer, through the Task.Create Cancel function or a token passed into CLR work:
cts := Clr.System.Threading.CancellationTokenSource() t := api.FooAsync(url, cts.Token) cts.Cancel()
IsCanceled then becomes true.
Clr.System.Threading.Tasks.Task.Delay(ms) and .FromResult(value) return Tasks; race Delay with WhenAny for a timeout. To run script code on another thread, use RealThread.
An error raised by a Task.Create producer or a Then callback becomes that operation's failed Task without calling OnError at the throw site. A failure nobody ever looks at is reported as an ordinary script error when the task is collected.
Waiting for an asynchronous .NET call without freezing the script.
#Import "Ks" { Task, Clr, Await }
http := Clr.System.Net.Http.HttpClient()
body := Await(http.GetStringAsync("https://example.com"))
MsgBox StrLen(body) " characters"
Running two pieces of work at once, then joining them.
#Import "Ks" { Task, Clr, Await }
http := Clr.System.Net.Http.HttpClient()
a := http.GetStringAsync("https://example.com")
b := http.GetStringAsync("https://example.org")
Await(Task.WhenAll(a, b))
MsgBox StrLen(a.Result) " and " StrLen(b.Result)
Reacting without blocking at all.
#Import "Ks" { Task, Clr, Await }
http := Clr.System.Net.Http.HttpClient()
http.GetStringAsync("https://example.com").Then(Show)
Show(body) => MsgBox(StrLen(body) " characters")
Racing the user against real work with Task.Create.
#Import "Ks" { Task, Clr, Await }
gate := Task.Create(Succeed => Hotkey("Esc", (*) => Succeed("cancelled")))
http := Clr.System.Net.Http.HttpClient()
winner := Await(Task.WhenAny(gate, http.GetStringAsync("https://example.com")))
MsgBox winner = "cancelled" ? "you gave up" : "downloaded " StrLen(winner) " characters"
Await, RealThread, Clr, #CSharp, Threads