考虑以下代码:
class Cat {
var name = "Tom"
}
class Globals {
var cat = Cat()
}
let glob = Globals()
func one () {
Task {glob.cat.name="Max"} // Expected Warning about some nonSendable moving into a different concurrency domain
}
Run Code Online (Sandbox Code Playgroud)
通常,-warn-concurrency启用后,Swift/Xcode 会警告不可发送的内容跨并发域。
根据我的理解,传递给的闭包Task必须始终是一个@Sendable闭包。闭包@Sendable只能捕获 Sendables。但是,Globals 不是可发送类型。我预计会出现类似以下的警告
@Sendable在闭包中捕获具有不可发送类型“Globals”的“glob”
不会发出此类警告。
我有一种情况,我正在循环中创建许多 Cocoa 对象async/await,并且内存峰值,因为这些对象仅在循环结束时释放(而不是每次迭代)。
解决方案是使用autoreleasepool. 但是,我似乎无法autoreleasepool与async/await。
这是一个例子:
func getImage() async -> NSImage? {
return NSImage(named: "imagename") // Do some work
}
Task {
// This leaks
for _ in 0 ..< 1000000 {
let image = await getImage()
print(image!.backgroundColor)
}
}
Run Code Online (Sandbox Code Playgroud)
内存一路飙升至 220MB,这对我来说有点太多了。
通常,您可以将内部循环包装在 a 中autoreleasepool,它可以解决问题,但是当我使用函数尝试它时async,我收到此错误:
Cannot pass function of type '() async -> ()' to parameter expecting synchronous function type
Run Code Online (Sandbox Code Playgroud)
有没有办法解决?或者是否有另一种方法可以实现在循环内释放 Cocoa 对象的相同目标?
我在一个简单的 Mac 测试项目中有以下代码:
\n@main\nclass AppDelegate: NSObject, NSApplicationDelegate {\n func applicationDidFinishLaunching(_ aNotification: Notification) {\n print("are we initially on the main thread: \\(Thread.isMainThread)")\n\n Task {\n print("is new task on main thread: \\(Thread.isMainThread)")\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n在Swift 语言指南中,我看到以下内容:
\n\n\n要创建在当前参与者上运行的非结构化任务,请调用 Task.init(priority:operation:) 初始值设定项。要创建不属于当前参与者的非结构化任务(更具体地称为分离任务),请调用 Task.detached(priority:operation:) 类方法。
\n
因此,由于 AppDelegate 代码在主要参与者上运行,并且我正在创建一个正常(即非分离)任务,因此我希望它的子任务也在主要参与者上运行。
\n但当我运行这个测试应用程序时,情况并非如此:
\nare we initially on the main thread: true\nis new task on main thread: false\nRun Code Online (Sandbox Code Playgroud)\n根据我所读到的有关 Swift 并发性的内容,我预计新任务将在主要参与者上调度并运行,因此Thread.isMainThread在该任务中也是如此。
为什么事实并非如此?
\n这是苹果开发者文档\xe3\x80\x82中的代码
\nlet url = URL(string: "https://example.com")!\n@State private var message = "Loading..."\n\nvar body: some View {\n Text(message)\n .task {\n do {\n var receivedLines = [String]()\n for try await line in url.lines {\n receivedLines.append(line)\n message = "Received \\(receivedLines.count) lines"\n }\n } catch {\n message = "Failed to load"\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n为什么不在messageUI 线程中更新,如下代码所示
DispatchQueue.main.async {\n message = "Received \\(receivedLines.count) lines"\n}\nRun Code Online (Sandbox Code Playgroud)\n任务块中的代码是否始终在UI线程中运行?
\n这是我的测试代码。有时任务似乎没有继承其调用者的参与者上下文。
\nfunc wait() async {\n await Task.sleep(1000000000)\n}\n\nThread.current.name = "Main thread"\nprint("Thread in top-level is \\(Thread.current.name)")\n\nTask …Run Code Online (Sandbox Code Playgroud) 与现有代码集成actors似乎并不像苹果希望您相信的那么简单。考虑以下简单的参与者:
actor Foo {
var value: Int = 0
}
Run Code Online (Sandbox Code Playgroud)
AppKit/UIKit尝试从任何(无任务)控制器访问此属性都是行不通的,因为每个Task控制器都是异步的。
class AppKitController {
func myTaskLessFunc() {
let f = Foo()
var v: Int = -1
Task { v = await f.value }
}
}
Run Code Online (Sandbox Code Playgroud)
这会给你带来预期的错误Mutation of captured var 'v' in concurrently-executing code,而且如果你试图锁定它,它也不会工作。nonisolated演员方法也没有帮助,因为你会遇到同样的问题。
那么,如何在无任务上下文中同步读取参与者属性呢?
我们可以创建一个@MainActor Task这样的:
Task { @MainActor in
print("hi")
}
Run Code Online (Sandbox Code Playgroud)
但也有@MainActor(unsafe)这样的:
Task { @MainActor(unsafe) in
print("hi")
}
Run Code Online (Sandbox Code Playgroud)
这两种方法有什么区别?
在试验 Swift 的并发性时,我希望有一个干净的 API 来公开给定元素类型的异步序列及其节流版本:
var intStream: AsyncStream<Int> {
AsyncStream<Int>(Int.self, bufferingPolicy: .bufferingNewest(5)) { continuation in
Task.detached {
for _ in 0..<100 {
try await Task.sleep(nanoseconds: 1 * 1_000_000_000)
continuation.yield(Int.random(in: 1...10))
}
continuation.finish()
}
}
}
var throttledIntStream: AsyncStream<Int> {
intStream.throttle(for: .seconds(2))
}
Run Code Online (Sandbox Code Playgroud)
但这不起作用,因为节流阀返回其自己的类型:错误:
Cannot convert return expression of type 'AsyncThrottleSequence<AsyncStream<Int>, ContinuousClock, Int>' to return type 'AsyncStream<Int>'
为了获得类型擦除我可以做
var throttledIntStream: some AsyncSequence {
intStream.debounce(for: Duration.seconds(2))
}
Run Code Online (Sandbox Code Playgroud)
但随后我也丢失了我想保留的元素类型信息。
有什么建议如何最好地解决这个问题?
编辑:这指向我想要的解决方案,但我想我需要等待https://forums.swift.org/t/anyasyncsequence/50828/2
我想了解是否需要考虑以下代码块将任务本身声明为 MainActor。
func login() {
Task { [weak self] in
let result = await self?.loginService.start()
if result == .successful {
self?.showWelcomeMessage() // updating the UI here
}
}
}
final class LoginService {
@MainActor
func start() async -> LoginResult {
// Doing some UI related operations here
}
}
Run Code Online (Sandbox Code Playgroud)
当任务内部调用的异步函数已经声明为 MainActor 时,我是否还需要将任务本身声明为 MainActor ?像这样:
func login() {
Task { @MainActor [weak self] in
let result = await self?.loginService.start()
if result == .successful {
self?.showWelcomeMessage() // updating the UI here
} …Run Code Online (Sandbox Code Playgroud) 我正在尝试使一个类符合Sendable. 我有一些可变的存储属性导致了问题。但是,我无法理解的是 MainActor 独立属性不允许我的类符合 Sendable。但是,如果我将整个班级标记为 a @MainActor,那就没问题了。然而,我实际上并不想让全班都遵守@MainActor。
举个例子,看下面的代码:
final class Article: Sendable {
@MainActor var text: String = "test"
}
Run Code Online (Sandbox Code Playgroud)
它给出了这样的警告:Stored property 'text' of 'Sendable'-conforming class 'Article' is mutable。
有人可以解释为什么吗?我认为与演员隔离会很好。
在《Swift 编程语言》并发章节的介绍部分中, 我读到:
\n\n\n当异步函数恢复时,Swift 不会对该函数将在哪个线程上运行做出任何保证。
\n
这让我很惊讶。与等待 pthread 中的信号量相比,执行可以跳转线程,这似乎很奇怪。
\n这让我产生以下问题:
\n为什么 Swift 不保证在同一个线程上恢复?
\n是否有任何规则可以\n确定恢复线程?
\n有没有办法影响这种行为,例如确保它在主线程上恢复?
\n编辑:我对 Swift 并发性和上述后续问题的研究是由于发现在主线程Task(在 SwiftUI 中)上运行的代码正在另一个线程上执行它的块而触发的。