🧵
await MainActor.run { ... }とawait Task { (at)MainActor in ... }の違い
はじめに
Swift ConcurrencyでのMainActor.run
というクラスメソッド呼び出しと、よくあるTask { @MainActor in ... }
に違いはあるのかどうかというはなし。結果違いがある。
もし何かご意見などあればコメントを下さい
最初に結論
- 違いはあるが公式リファレンス見ても分かりづらい
-
MainActor.run
の特徴- クロージャに入れられるのは
MainActor
のSendable
に準拠してるかチェックされてそう
- クロージャに入れられるのは
具体例
違いが明確にならない例
まずMainActor.run
のわかりやすい例として次のようなのがある
func couldBeAnywhere() async {
await MainActor.run {
print("This is on the main actor.")
}
}
そしてTask { @MainActor in ... }
の例が次。
func couldBeAnywhere() async {
let task = Task { @MainActor in
print("This is on the main actor.")
}
await task.value
}
// もしくは
func couldBeAnywhere2() async {
await Task { @MainActor in
print("This is on the main actor.")
}.value
}
この例での比較はシンプルすぎて違いはない。
違いが明確になる例
MainActor.run
を入れ子にする例。コンパイルできない。
func couldBeAnywhere() async {
await MainActor.run { // ここでコンパイルエラー
print("This is on the main actor.")
await MainActor.run {
print("This is on the main actor.")
}
}
}
上記コードによるコンパイルエラーは
error: cannot pass function of type '@Sendable () async -> ()' to parameter expecting synchronous function type
そしてTask { @MainActor in ... }
の例が次。コンパイルできる。
func couldBeAnywhere2() async {
await Task { @MainActor in // コンパイルエラーにならない
print("This is on the main actor.")
await Task { @MainActor in
print("This is on the main actor.")
}.value
}.value
}
なぜコンパイルエラー?と思って.swiftinterfaceファイル(XcodeからJump To Difinition)を見ると
まずMainActor.run
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension MainActor {
/// Execute the given body closure on the main actor.
public static func run<T>(
resultType: T.Type = T.self,
body: @MainActor @Sendable () throws -> T
) async rethrows -> T where T : Sendable
}
次にTask.init
extension Task where Failure == Never {
... 省略
public init(
priority: TaskPriority? = nil,
operation: @escaping @Sendable () async -> Success
)
}
... 省略
extension Task where Failure == Error {
... 省略
public init(
priority: TaskPriority? = nil,
operation: @escaping @Sendable () async throws -> Success
)
}
なるほど、本題として気がつくところは
-
MainActor.run
のbodyクロージャは-
@MainActor @Sendable () thorws -> T
- つまり、クロージャ内では
@MainActor
なものしか入れられない(?)
- つまり、クロージャ内では
-
感想
-
await Task { @MainActor in ... }.value
と長く書くぐらいならawait MainActor.run { ... }
で十分って感じがする -
await MainActor.run { ... }
の特徴- 優先度を自前で設定できないが
await Task { @MainActor in ... }.value
するとき、valueしちゃってるんでそもそも優先度指定に意味があるとも思えない。すでに実行されてawaitしてるわけだからその場で実行される- つまりTaskが優先度を指定できてもそれはMainActor.runに対するメリットではない
- 優先度を自前で設定できないが
参考
- hakingwithswiftの記事自体は、違いのあるなしということを論じてないが参考になった
Discussion