gpt4 book ai didi

Swift: Silence "Non-Sendable Notification? cannot cross actor boundary" warning(SWIFT:沉默“不可发送的通知?无法跨越参与者边界”警告)

转载 作者:bug小助手 更新时间:2023-10-28 21:19:16 27 4
gpt4 key购买 nike



Problem


I have a Mac app that needs to perform an action when the Mac sleeps. To do this, I'm using this "modern" approach to listen for the notification:

我有一个Mac应用程序,它需要在Mac休眠时执行一个操作。要做到这一点,我使用这种“现代”方法来监听通知:


@MainActor
final class AppController: NSObject, ObservableObject
{
var sleepTask: Task<Void, Never>? = nil

override init()
{
sleepTask = Task { [weak self] in
for await _ in NSWorkspace.shared.notificationCenter.notifications(named: NSWorkspace.willSleepNotification)
{
self?.doSomething()
}
}
}
}

Xcode 15 beta 8 has introduced a new warning on the for/await call:

Xcode 15测试版8在for/await调用中引入了新的警告:



Non-sendable type 'Notification?' returned by implicitly asynchronous call to nonisolated function cannot cross actor boundary

1. Generic enum 'Optional' does not conform to the 'Sendable' protocol (Swift.Optional)



enter image description here


But I'm not using the notification object at all; it's not crossing the actor boundary.

但我根本没有使用通知对象;它没有跨越参与者边界。


Question:


How can I silence this warning? (Other than forcing Optional to conform to @unchecked Sendable).

我怎么才能让这个警告消失呢?(除了强制OPTIONAL遵守@Unchecked Sendable)。


更多回答

I don’t even know where the optional came from! .notifications returns NotificationCenter.Notifications, whose .Element is the non-optional Notification.

我甚至不知道可选的是从哪里来的!.Notiments返回NotificationCenter.Notiments,其.Element是非可选的通知。

@Sweeper - I'll update the example with less truncated code

@Sweeper-我将使用较少的截断代码更新示例

Is this the real code? If this inherits from NSObject the compiler will complain about not overriding init.

这是真正的密码吗如果这是从NSObject继承的,编译器会抱怨没有覆盖init。

@vadian - It is. In my actual app there is a parameter in init(), which I omitted here because it's not relevant. I'll add the override keyword for pedantic completeness.

@vadian--没错。在我的实际应用程序中,init()中有一个参数,我在这里省略了它,因为它不相关。我将为迂腐的完备性添加覆盖关键字。

And why do you run doSomething on another MainActor Task as the class (and also doSomething) runs on the main actor anyway?

为什么要在另一个主参与者任务上运行DoSomething,因为类(以及DoSomething)在主参与者上运行?

优秀答案推荐

You said:

你说过:



How can I silence this warning? (Other than forcing Optional to conform to @unchecked Sendable).



You can:

你可以:



  1. You can declare Notification to be Sendable. E.g.,


    extension Notification: @unchecked Sendable { }

    This is disquieting. It feels like this struct should be Sendable, but it’s not clear why it isn't. I don't know whether they just have not gotten around to it yet or whether there is something going on internally that prevents them from doing so.



  2. You can also change your sequence to yield something other than a Notification:


    sleepTask = Task { [weak self] in
    let sequence = NSWorkspace
    .shared
    .notificationCenter
    .notifications(named: NSWorkspace.willSleepNotification)
    .map { _ in () }

    for await _ in sequence {
    self?.doSomething()
    }
    }




This is not what you asked, but you don't need (and shouldn't use) a Task within a Task, and you don't need (and shouldn't use) a reference to your Task. The following will do what you want done:

这不是您所要求的,但是您不需要(也不应该使用)任务中的任务,并且不需要(也不应该使用)对您的任务的引用。以下操作将完成您想要完成的操作:


@MainActor
final class AppController: NSObject, ObservableObject {
override init() {
super.init()
Task { [weak self] in
let center = NSWorkspace.shared.notificationCenter
let willSleep = NSWorkspace.willSleepNotification
for await _ in center.notifications(named: willSleep).map({ _ in }) {
self?.doSomething()
}
}
}
func doSomething() {}
}

Properly speaking, you should also add a break that cancels the task just in case self (the AppController) goes out of existence. I take it that it will not go out of existence, but it is best to make a habit of doing things properly:

正确地说,您还应该添加一个中断,以取消任务,以防self(AppController)不存在。我认为它不会消失,但最好养成正确做事的习惯:


@MainActor
final class AppController: NSObject, ObservableObject {
override init() {
super.init()
Task { [weak self] in
let center = NSWorkspace.shared.notificationCenter
let willSleep = NSWorkspace.willSleepNotification
for await _ in center.notifications(named: willSleep).map({ _ in }) {
guard let self else { break }
doSomething()
}
}
}
func doSomething() {}
}


Let's see first where your problem comes from.

让我们先看看你的问题是从哪里来的。



  1. AsyncSequence requires defining an iterator that has a func next() async -> Notification? method used by the runtime to know when to end the for loop. Thus, behind the scenes, the for loop behaves more like a while:
    var asyncIterator = NSWorkspace.shared.notificationCenter.notifications(named: 
    NSWorkspace.willSleepNotification).makeAsyncIterator()
    while let notification = await asyncIterator.next() {
    self?.doSomething()
    }


  2. The notifications are delivered on their own async execution context, while the task you create in init inherits the execution context, meaning it will execute on the main actor. This means the notification objects will have to be passed between different concurrent execution contexts, hence the warning.


Solutions to fix the warning have already been provided, but since I don't like to write an answer without also giving a possible solution, here is one from me: use a detached task.

解决警告的方法已经提供了,但是因为我不喜欢在没有给出可能的解决方案的情况下写一个答案,这里是我的一个:使用分离任务。


sleepTask = Task.detached { [weak self] in
for await _ in NSWorkspace.shared.notificationCenter.notifications(named: NSWorkspace.willSleepNotification)
{
await self?.doSomething()
}
}

A detached task doesn't inherit the current actor execution context, which means the for loop will run in the same execution context as the publisher of the notifications. But, this implies that:

分离的任务不继承当前执行元执行上下文,这意味着for循环将在与通知发布者相同的执行上下文中运行。但是,这意味着:



  1. in order to hop back on the main actor, you'll need to await the doSomething call. I don't see this as a problem, au-contraire, it might make things more obvious.

  2. you'll need to make sure to cancel the task in the deinit of AppController, otherwise the for-loop will run indifinetely. But this is also the case with the "non-detached" task, any unstructured tasks you create will need to be "manually" cancelled, otherwise they will leak resources.


更多回答

The .map is what we do in our app.

.map是我们在应用程序中执行的操作。

“It feels like this struct should be Sendable, but it’s not clear why it isn't.” I’m guessing it’s just a wrapper around a reference to an NSNotification.

“感觉这个结构应该是可发送的,但不清楚为什么不是。”我猜这只是对NSNotify引用的包装。

Agreed. But why isn’t that Sendable, too? Does it have mutable state? Its exposed properties are all immutable…

同意。但为什么这不也是可以发送的呢?它的状态是可变的吗?它公开的属性都是不可变的…

Thanks! Out of curiosity: is option 1 the only route if I actually DO need to use the notification object?

谢谢!出于好奇:如果我确实需要使用通知对象,选项1是唯一的途径吗?

No. If you “need” the notification object, it’s generally because you need to extract stuff from the userInfo. So I have map of the sequence in option 2 extract what I need. Option 1 is if you think (like I suspect) that the lack of Sendable for Notification is just a pre-Swift 6 preconcurrency oversight. As a general rule, marking something that is not Sendable as such is a very bad idea, but I personally suspect this Notification issue is just a temporary state of affairs, something that Apple is likely to fix before Swift 6 comes around.

不是的。如果您“需要”通知对象,通常是因为您需要从userInfo中提取内容。所以我有了选项2中的序列地图,提取我需要的东西。选项1是如果您认为(就像我怀疑的那样)缺少Sendable for Notification只是SWIFT 6之前的预并发疏忽。一般来说,将不可发送的东西标记为不可发送是一个非常糟糕的主意,但我个人怀疑这个通知问题只是一个暂时的问题,苹果很可能会在Swift 6出现之前解决这个问题。

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com