Core Types
-
A protocol that defines a container for the result of an asynchronous operation, such as an HTTP request, a timeout, a disk I/O operation, etc.
Futures can be pending, meaning that the result of the operation is not available yet, or completed, meaning that the result is ready. Futures can be combined into larger operations with combinators, such as
map(_:)
,flatMap(_:)
, etc.Futures communicate a single value of type
Output
. Semantically, futures capture only the notion of completion; that is, there is no explicit distinction between a successful or a failed operation. Operations that must communicate success or failure, must do so by encoding that information in the output type, typically usingSwift.Result
. Futures comes with a number of convenience types and combinators for working withSwift.Result
(seeResultFuture
).You typically create futures using the static convenience methods on the
Future
namespace. You can also create custom futures by adopting this protocol in your types.Creating futures is always an asynchronous operation. The builtin types guarantee that the operation the future wraps will only be started after the future is submitted to an executor (see
ExecutorProtocol
). Custom future implementations must also provide the same guarantee.Memory and Concurrency Management
When a future is submitted to an executor, ownership of the future and its resources is transferred into the executor. You can only affect its fate indirectly from that point on (see
Task
).When a future completes, it is effectively dropped and its resources are released on the same execution context (eg. a thread or a Dispatch queue) the future was started and polled in.
This makes both memory and concurrency management predictable and extremely easy to reason about; a future’s lifetime is wholly contained within the executor it was submitted on.
Cancellation
Futures support cancellation implicitly. As discussed above, for a future to make progress, it must be polled by an executor. By simply not polling the future and instead dropping it effectively cancels the underlying operation. To explicitly cancel a submitted future, use
Task
.If the operation is allocating resources that must be released manually, you can provide a
See morecancellation token
at creation which will automatically invoke a given closure upon deallocation, be it due to completion or cancellation (seeDeferred
).Declaration
Swift
public protocol FutureProtocol : FutureConvertible where Self == Self.FutureType
-
A protocol that defines an abstraction for a series of asynchronously produced discrete values over time, such as byte buffers read from a socket or a file, incoming requests to a server, or sampled mouse input events.
Streams yield elements. Similar to Swift’s
IteratorProtocol
, streams signify completion by yieldingnil
. Streams can be combined into larger operations with combinators, such asmap()
,flatMap()
, etc.The type of element yielded by a stream is specified by its
Output
type. Streams that must communicate success or failure, must do so by encoding that information in the output type, typically usingSwift.Result
. Futures comes with a number of convenience types and combinators for working withSwift.Result
(seeFuturesResult
module).You typically create streams using the convenience methods on
Stream
. You can also create custom streams by adopting this protocol in your types. Creating streams is always an asynchronous operation. It is guaranteed that the producer of elements the stream wraps will only be asked for elements after the stream is submitted to an executor and it will always be on that executor’s context (seeExecutorProtocol
). In other words, streams do nothing unless submitted to an executor.For a stream to be submitted to an executor and yield elements, it must be converted to a future that represents the stream’s completion. As a convenience, this is done automatically if the stream output is
Void
.The semantics for cancellation, and memory and concurrency management are the same as for futures (see
See moreFutureProtocol
).Declaration
Swift
public protocol StreamProtocol : StreamConvertible where Self == Self.StreamType
-
Declaration
Swift
public protocol SinkProtocol : SinkConvertible where Self == Self.SinkType
-
A protocol that defines primitives for unidirectional communication between a sender and a receiver task. The sending side of a channel is convertible to a sink and the receiving to a stream.
Channels are categorized into different flavors based on whether they support a single or multiple senders and whether they apply backpressure when their internal buffer reaches a configurable limit.
Channels can be explicitly closed by either the sending or receiving side. Dropping either side causes the channel to close automatically. Senders are prevented from sending new items into a closed channel. Any items buffered by the channel, however, can still be taken out by the receiver.
Senders can request to be notified when the channel is flushed. Flushing is not an exclusive operation; other senders are not prevented from sending an item, or even closing the channel. Therefore flushing is useful primarily with single-sender channels. Flushing multi-sender channels is less reliable because of the indeterminism inherent in that configuration – a sender may request a flush and by the time it returns to check that flushing completed another sender running on another thread might have sent a new item. You have to externally coordinate the senders in this case.
See moreDeclaration
Swift
public protocol ChannelProtocol
-
A protocol that defines objects that execute futures.
Executors provide the context in which futures execute. This context may be the current or a background thread, a Dispatch queue, a run loop, etc. Executors typically enqueue submitted futures in a
ready to run
queue and immediately return to the caller. This queue is later on consumed by the executor in its execution context, driving futures to completion by polling them. The polling algorithm is roughly:var context = self.makeContext() while var future = self.readyToRunQueue.pop() { if future.poll(&context).isReady { self.drop(future) } else { self.pendingFutures.push(future) } }
Futures that are ready to run are polled by the executor. If the future returns that it completed (see
Poll
), it is let go by the executor, which causes the future and associated resources to be released. Otherwise, it is added into apending futures
registry. Before returning, the future registers the context’s waker (seeContext
andWakerProtocol
) to receive a notification when the external event the future is waiting on fires. As a result of the notification, the future is added back into theready to run
queue and the process repeats until the future signifies completion.Futures are submitted for execution via the
trySubmit(_:)
method of an executor. Executors guarantee that submitted futures will be executed asynchronously. In other words,trySubmit(_:)
merely schedules the future to be executed at some point in the future and returns to the caller immediately. This is an important invariant that custom implementations must also maintain, since it removes all concerns about reentrancy.Executors may deny to receive the future, in which case
trySubmit(_:)
returns an appropriate error of typeFailure
. This can be the case when the executor is shutting down or is at capacity and needs to provide backpressure. Executors that never fail, declare theirFailure
type asNever
.This protocol makes no guarantees on the safety of concurrent calls to
See moretrySubmit(_:)
. Each executor implementation is free to declare support for concurrent submissions, documenting the fact accordingly. This ambiguity is not a problem in practice since you typically use concrete executor types so you always know the specifics. The built-inQueueExecutor
is an executor that supports concurrent submissions.Declaration
Swift
public protocol ExecutorProtocol : AnyObject
-
A handle to a unit of work that is executing in an executor.
In Futures, task is both a high-level concept and a type. As a concept, it is the outermost future that is submitted into an executor for execution. As a type, it is a handle to the conceptual task, which can be used to extract the result of the computation or cancel it altogether.
Tasks are futures themselves and can be seamlessly combined with other tasks or futures.
Task
guarantees that:- the wrapped future is cancelled if the task is dropped.
- the wrapped future is destroyed on the executor’s context it was submitted on.
- the executor is kept alive for as long as the task itself is kept alive.
To create a task, use the
ExecutorProtocol.trySpawn(...)
method.To cancel the task, invoke
See morecancel()
on it.Declaration
Swift
public final class Task<Output> : FutureProtocol, Cancellable