🧵

await MainActor.run { ... }とawait Task { (at)MainActor in ... }の違い

2022/08/29に公開

はじめに

Swift ConcurrencyでのMainActor.runというクラスメソッド呼び出しと、よくあるTask { @MainActor in ... }に違いはあるのかどうかというはなし。結果違いがある。

もし何かご意見などあればコメントを下さい

最初に結論

具体例

違いが明確にならない例

まず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なものしか入れられない(?)

感想

参考

https://www.hackingwithswift.com/quick-start/concurrency/how-to-use-mainactor-to-run-code-on-the-main-queue

  • hakingwithswiftの記事自体は、違いのあるなしということを論じてないが参考になった

Discussion

ログインするとコメントできます