What’s the inverse of `MainActor.assertIsolated`?

In Swift 6, Thread.isMainThread is illegal in an async context. To prove you’re “on the main thread”, you now assert you’re on the main actor:

MainActor.assertIsolated()

That line will crash if we’re not on the main actor (in a debug build), and so your assumptions are confirmed when you don’t crash.

My question is: Is there a way to assert that you’re not on the main actor? If I have another actor, I can use assertIsolated() with that actor, of course; but I’m talking about the nonisolated actor, the “default” actor that the runtime supplies for situations where there is no named actor. What is the name of that actor, to which I can send assertIsolated()? Or is there any way, short of crashing, to confirm that I’m not on the main actor?

11

tl;dr:

  1. If you want to know whether a nonisolated async func is running on the main actor: it never will be
  2. If you want to know whether a nonisolated async func is running on the main thread: you’ll need to wrap Thread.isMainThread/pthread_main_np in C/Objective-C/Swift to make it callable in async contexts

If you look at the implementation of MainActor.assumeIsolated(), all it’s really doing is grabbing the actor’s executor and checking whether the current task is running on that executor (using a low-level silgen’d function).

Given the tools available from outside of the Concurrency module, you can’t implement a similar (but inverse) check yourself, but digging into the docs shows that this doesn’t appear to be necessary:

Executor Types

The low-level interface for concurrency underpinning Tasks and Actors is ExecutorJobs running on Executors. Every Actor has an associated Executor that performs the actual work, and every Task is broken up into ExecutorJobs and submitted to that executor. (A job is all of the synchronous work in a task between two await statements)

The Executor protocol is bifurcated into two mutually-exclusive sub-protocols:

  1. SerialExecutor, and
  2. TaskExecutor

SerialExecutors are the executors defined by Actors to perform their work:

  • If an actor specifies an unownedExecutor, all work done on that actor will be submitted to its executor
  • If an actor does not specify an unownedExecutor, a default executor is provided which uses the global concurrent thread pool

nonisolated async functions, however, are not isolated to a specific actor, and thus don’t use their executors; instead, they either:

  1. Run on a preferred TaskExecutor if a preference is specified via withTaskExecutorPreference(_:isolation:operation:) (or similar), or
  2. Run on the global concurrent executor

From the docs:

By default, without setting a task executor preference, nonisolated asynchronous functions, as well as methods declared on default actors – that is actors which do not require a specific executor – execute on Swift’s default global concurrent executor. This is an executor shared by the entire runtime to execute any work which does not have strict executor requirements.

From a much more specific doc comment in the code:

/// [ func / closure ] - /* where should it execute? */
///                               |
///                     +--------------+          +===========================+
///           +-------- | is isolated? | - yes -> | actor has unownedExecutor |
///           |         +--------------+          +===========================+
///           |                                       |                |
///           |                                      yes               no
///           |                                       |                |
///           |                                       v                v
///           |                  +=======================+    /* task executor preference? */
///           |                  | on specified executor |        |                   |
///           |                  +=======================+       yes                  no
///           |                                                   |                   |
///           |                                                   |                   v
///           |                                                   |    +==========================+
///           |                                                   |    | default (actor) executor |
///           |                                                   v    +==========================+
///           v                                     +==============================+
///  /* task executor preference? */ ---- yes ----> | on Task's preferred executor |
///           |                                     +==============================+
///           no
///           |
///           v
///  +===============================+
///  | on global concurrent executor |
///  +===============================+

Importantly:

  1. The global concurrent executor is never going to be the main actor executor, and
  2. All of the Task executor preference APIs take a TaskExecutor, which cannot be a specific actor’s SerialExecutor, unless the executor publicly adopts both protocols or offers a way to expose itself as a task executor too

So, for a given nonisolated async function, you shouldn’t need to check that you’re not on the main actor, because you never will be.

Caveat

Crucially: the main actor is not the same thing as the main thread/queue, in that the main actor is guaranteed to execute on the main thread/queue, but other executors might too.

The global pool does not guarantee any thread (or dispatch queue) affinity

Theoretically, the global thread pool could include the main thread (though I don’t think this can really be done safely in practice), so just checking that you’re not on the main actor does not necessarily imply that you’re not on the main thread.

In the general case, if you really need to check whether you’re on the main thread specifically, you’re going to need to wrap Thread.isMainThread/pthread_main_np in your own C/Objective-C/Swift code to bypass the unavailable-in-Swift-concurrency limitations.

3

Taking a hint from Itai Ferber’s answer, I can work around the restriction by hiding the call to Thread.isMainThread behind a nonisolated(unsafe) wall:

nonisolated(unsafe) let swiftThreadReporter = MySwiftThreadReporter()
class MySwiftThreadReporter {
    func isOnMainThread() -> Bool {
        return Thread.isMainThread
    }
}

Now any code can say swiftThreadReporter.isOnMainThread() and get back the right answer. This is a good enough solution to allow me to move forward.

You probably wouldn’t want to include that code in a shipping application, but I wasn’t going to anyway; the goal is merely to print to learn whether I’ve gotten off the main thread. The caveats from Itai’s answer still apply, obviously; in particular, there isn’t a perfect one-to-one correspondence between the main thread and the main actor.

I still feel like Apple has a left us with a hole in the language here. If they can give us assertIsolated() they should be able to give us assertNotIsolated() (meaning, not “is nonisolated”, but “is not isolated to the actor to whom this method is sent”).

While I confess that I have used this trick, myself, I have grown wary of this approach (a check of Thread.isMainThread wrapped in a synchronous function). Yes, it lets us examine isMainThread without the compiler warning/error, but there are likely reasons (beyond just abstracting us away from thinking about threads) why Apple went through all the work of adding a warning if you use this property from an asynchronous context.

As far as I know, Apple has not specifically articulated their rationale for disabling this in Swift concurrency contexts, but the concern might be that one might draw incorrect conclusions and/or sacrifice some clever optimizations. The one that jumps out at me is partial tasks, whereby the compiler performs a static analysis of the code and determines whether the partial task (i.e., the code after an await) actually requires a hop back to the main task’s isolation context, and if not, it optimizes that out, entirely. It is an elegant little performance optimization, if not immediately intuitive.

In previous versions of the Swift compiler, when people were first learning Swift concurrency, and using isMainThread to validate their understanding, many were confused by some of the arcane behaviors (e.g., @MainActor closure does not seem to execute on the main thread deterministically). In recent versions of the compiler, these behaviors have been harder to reproduce, as it appears that inserting even nonisolated code in a partial task appears to reintroduce the hop that might otherwise have been optimized out. The optimization is still there if you don’t have anything in the continuation, e.g., two successive await calls to a separate context (verified by adding breakpoints and examining what thread you are on), but it appears to not happen in the face of any code in the continuation. But I would be hesitant to rely on this undocumented behavior (and might even welcome the return of the previous, admittedly aggressive, optimization).

Your point still stands, that it would be nice if there was a way to confirm that you are not on a particular actor/thread, if only for diagnostic purposes, even if we sacrificed some Swift concurrency optimizations. Especially since they’ve introduced the executor-preference logic for nonisolated async functions (which, as an aside, is one of my least favorite implementations), the ability to have these sorts of diagnostics would be useful. Something akin to GCD‘s dispatchPrecondition(condition: .notOnQueue(.main)) would be nice, but I can imagine all sorts of reasons why they might be reluctant to introduce that in Swift concurrency.

That having been said, the need for these sorts of preconditions is somewhat diminished in Swift concurrency: Back in the GCD days, the caller generally dictated what thread some code was running on, so these preconditions were fairly essential when coding defensively. But in Swift concurrency, the context is generally dictated by the called routine’s definition (with some exceptions), so it the argument for introducing more preconditions is a little less compelling.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật