Accessing a Thread
Using Future<T>
to Wait for Thread Completion and Obtain Return Value
In the preceding example, the newly created thread ends in advance because the main thread ends. If the sequence is not ensured, the newly created thread may even exit before it is executed. You can use the return value of the spawn
expression to wait until the thread execution is complete.
The return type of the spawn
expression is Future<T>
, where T
is a type variable and its type is the same as that of the lambda expression. When you call the get()
member function of Future<T>
, it waits for its thread to complete execution.
The prototype declaration of Future<T>
is as follows:
public class Future<T> {
// Blocking the current thread, waiting for the result of the thread corresponding to the current Future object.
// If an exception occurs in the corresponding thread, the method will throw the exception.
public func get(): T
// Blocking the current thread, waiting for the result of the thread corresponding to the current Future object.
// If the corresponding thread has not completed execution within ns nanoseconds, the method will return an Option<T>.None.
// If `ns` <= 0, its behavior is the same as `get()`.
public func get(ns: Int64): Option<T>
// Non-blocking method that immediately returns Option<T>.None if thread has not finished execution.
// Returns the computed result otherwise.
// If an exception occurs in the corresponding thread, the method will throw the exception.
public func tryGet(): Option<T>
}
The following example code demonstrates how to use Future<T>
to wait for the execution of a newly created thread in main
:
import std.sync.*
import std.time.*
main(): Int64 {
let fut: Future<Unit> = spawn { =>
println("New thread before sleeping")
sleep(100 * Duration.millisecond) // sleep for 100 ms.
println("New thread after sleeping")
}
println("Main thread")
fut.get() // wait for the thread to finish.
return 0
}
When the get()
of the Future<T>
instance is called, the current thread is blocked until the thread represented by the Future<T>
instance ends. Therefore, information similar to the following may be displayed in the preceding example:
New thread before sleeping
Main thread
New thread after sleeping
After the print operation is complete, the main thread will wait for the newly created thread to finish executing because it calls get()
. However, the printing sequence of the main thread and new thread is uncertain.
But what happens if we move fut.get()
before the main thread prints? The following is an example:
import std.sync.*
import std.time.*
main(): Int64 {
let fut: Future<Unit> = spawn { =>
println("New thread before sleeping")
sleep(100 * Duration.millisecond) // sleep for 100 ms.
println("New thread after sleeping")
}
fut.get() // wait for the thread to finish.
println("Main thread")
return 0
}
The main thread will wait for the newly created thread to finish executing before it performs the print operation. Therefore, the output of the program is determined as follows:
New thread before sleeping
New thread after sleeping
Main thread
A call location of the get()
affects whether the threads can run concurrently.
In addition to waiting for the thread to finish, Future<T>
can also be used to obtain the thread execution result. Now, let's look at the specific member functions provided by it.
-
get(): T
: waits for the thread to finish executing and returns the execution result. If the thread has finished, the execution result is returned immediately.The sample code is as follows:
import std.sync.* import std.time.* main(): Int64 { let fut: Future<Int64> = spawn { sleep(Duration.second) // sleep for 1s. return 1 } try { // wait for the thread to finish, and get the result. let res: Int64 = fut.get() println("result = ${res}") } catch (_) { println("oops") } return 0 }
The output is as follows.
result = 1
-
get(ns: Int64): Option<T>
: waits for the thread represented byFuture<T>
to finish executing and returns the execution result. If the timeout ofns
is reached and the thread has not finished executing,Option<T>.None
is returned. Ifns <= 0
, the behavior is the same as that ofget()
.The sample code is as follows:
import std.sync.* import std.time.* main(): Int64 { let fut = spawn { sleep(Duration.second) // sleep for 1s. return 1 } // wait for the thread to finish, but only for 1 ms. let res: Option<Int64> = fut.get(1000 * 1000) match (res) { case Some(val) => println("result = ${val}") case None => println("oops") } return 0 }
The output is as follows.
oops
Accessing Thread Attributes
Each Future<T>
object has a corresponding Cangjie thread, which is represented by a Thread
object. The Thread
class is mainly used to access thread attributes, for example, a thread ID. Note that Thread
cannot be directly instantiated to construct an object. You can only obtain the corresponding Thread
object from the thread
member attribute of Future<T>
or obtain the Thread
object corresponding to the thread that is being executed through the static member attribute currentThread
of Thread
.
Some methods of the Thread
class are defined as follows. For details about the methods, see Cangjie Programming Language Library API.
class Thread {
... ...
// Get the currently running thread
static prop currentThread: Thread
// Get the unique identifier (represented as an integer) of the thread object
prop id: Int64
// Check whether the thread has any cancellation request
prop hasPendingCancellation: Bool
}
The following example code demonstrates how to obtain a thread ID in two ways after creating a thread. Since the main thread and the new thread obtain the same Thread
object, they can print the same thread ID.
main(): Unit {
let fut = spawn {
println("Current thread id: ${Thread.currentThread.id}")
}
println("New thread id: ${fut.thread.id}")
fut.get()
}
The output is as follows.
New thread id: 1
Current thread id: 1