SwiftUI | DispatchQueue main/global() sync/asyncの違いを実験で理解する

関数

SwiftUIのDispatchQueue main/global() sync/asyncの違いを実験で理解した結果を説明する。

結論

以下8パターンの実験を行った。

  • メインスレッドからDispatchQueue.main.sync
  • メインスレッドからDispatchQueue.main.async
  • メインスレッドからDispatchQueue.global().sync ★1
  • メインスレッドからDispatchQueue.global().async
  • 外部スレッドからDispatchQueue.main.sync ★2
  • 外部スレッドからDispatchQueue.main.async ★2
  • 外部スレッドからDispatchQueue.global().sync
  • 外部スレッドからDispatchQueue.global().async ★1

★1,★2は以下の特徴があることがわかった。

  • ★1 並列処理ができるため処理を高速化できる。
  • ★2 メインスレッドに処理を渡すことができる。

実験結果の概略を以下に図で示す。

メインスレッドから実行させた場合

.sync.async
mainエラー発生
global()

外部スレッドから実行させた場合

.sync.async
main
global()

以下に実験内容を示す。

実験1 メインスレッドから実行させる

DispatchQueue.main.sync

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("ボタンをタップ") {
            print("始め。", 今のスレッドを表示())
            iを数える()
            print("終わり。", 今のスレッドを表示())
        }
        .buttonStyle(.borderedProminent)
    }
    
    func iを数える() {
        for i in 0..<9 {
            DispatchQueue.main.sync {  // ? ここをいろいろ試す。
                sleep(1)
                print(i, 今のスレッドを表示())
            }
        }
    }
    
    func 今のスレッドを表示() -> String {
        return "\(Thread.current)"
    }
    
}

メインスレッドからDispatchQueue.main.syncを実行するとエラー Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) が発生する。

DispatchQueue.main.async

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("ボタンをタップ") {
            print("始め。", 今のスレッドを表示())
            iを数える()
            print("終わり。", 今のスレッドを表示())
        }
        .buttonStyle(.borderedProminent)
    }
    
    func iを数える() {
        for i in 0..<9 {
            DispatchQueue.main.async {  // ? ここをいろいろ試す。
                sleep(1)
                print(i, 今のスレッドを表示())
            }
        }
    }
    
    func 今のスレッドを表示() -> String {
        return "\(Thread.current)"
    }
    
}

全てメインスレッド(number = 1)で実行された。

始め。 <_NSMainThread: 0x600002c4c000>{number = 1, name = main}
終わり。 <_NSMainThread: 0x600002c4c000>{number = 1, name = main}
0 <_NSMainThread: 0x600002c4c000>{number = 1, name = main}
1 <_NSMainThread: 0x600002c4c000>{number = 1, name = main}
2 <_NSMainThread: 0x600002c4c000>{number = 1, name = main}
3 <_NSMainThread: 0x600002c4c000>{number = 1, name = main}
4 <_NSMainThread: 0x600002c4c000>{number = 1, name = main}
5 <_NSMainThread: 0x600002c4c000>{number = 1, name = main}
6 <_NSMainThread: 0x600002c4c000>{number = 1, name = main}
7 <_NSMainThread: 0x600002c4c000>{number = 1, name = main}
8 <_NSMainThread: 0x600002c4c000>{number = 1, name = main}

DispatchQueue.global().sync

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("ボタンをタップ") {
            print("始め。", 今のスレッドを表示())
            iを数える()
            print("終わり。", 今のスレッドを表示())
        }
        .buttonStyle(.borderedProminent)
    }
    
    func iを数える() {
        for i in 0..<9 {
            DispatchQueue.global().sync {  // ? ここをいろいろ試す。
                sleep(1)
                print(i, 今のスレッドを表示())
            }
        }
    }
    
    func 今のスレッドを表示() -> String {
        return "\(Thread.current)"
    }
    
}

DispatchQueue.main.asyncのときと似ているが「終わり。」が最後に実行された。全てメインスレッド(number = 1)で実行された。

始め。 <_NSMainThread: 0x600001600000>{number = 1, name = main}
0 <_NSMainThread: 0x600001600000>{number = 1, name = main}
1 <_NSMainThread: 0x600001600000>{number = 1, name = main}
2 <_NSMainThread: 0x600001600000>{number = 1, name = main}
3 <_NSMainThread: 0x600001600000>{number = 1, name = main}
4 <_NSMainThread: 0x600001600000>{number = 1, name = main}
5 <_NSMainThread: 0x600001600000>{number = 1, name = main}
6 <_NSMainThread: 0x600001600000>{number = 1, name = main}
7 <_NSMainThread: 0x600001600000>{number = 1, name = main}
8 <_NSMainThread: 0x600001600000>{number = 1, name = main}
終わり。 <_NSMainThread: 0x600001600000>{number = 1, name = main}

DispatchQueue.global().async

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("ボタンをタップ") {
            print("始め。", 今のスレッドを表示())
            iを数える()
            print("終わり。", 今のスレッドを表示())
        }
        .buttonStyle(.borderedProminent)
    }
    
    func iを数える() {
        for i in 0..<9 {
            DispatchQueue.global().async {  // ? ここをいろいろ試す。
                sleep(1)
                print(i, 今のスレッドを表示())
            }
        }
    }
    
    func 今のスレッドを表示() -> String {
        return "\(Thread.current)"
    }
    
}

メインスレッド(number = 1)以外のスレッド(number = 1以外)が9本立って並列に実行された。

始め。 <_NSMainThread: 0x600003fe02c0>{number = 1, name = main}
終わり。 <_NSMainThread: 0x600003fe02c0>{number = 1, name = main}
5 <NSThread: 0x600003fd8c40>{number = 4, name = (null)}
2 <NSThread: 0x600003fa8f80>{number = 5, name = (null)}
4 <NSThread: 0x600003fe5b40>{number = 3, name = (null)}
0 <NSThread: 0x600003f80080>{number = 7, name = (null)}
6 <NSThread: 0x600003f850c0>{number = 10, name = (null)}
7 <NSThread: 0x600003fb0000>{number = 11, name = (null)}
1 <NSThread: 0x600003fbd240>{number = 6, name = (null)}
8 <NSThread: 0x600003fa3180>{number = 12, name = (null)}
3 <NSThread: 0x600003fa3380>{number = 9, name = (null)}

実験2 外部スレッドから実行させる

DispatchQueue.main.sync

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("ボタンをタップ") {
            print("始め。", 今のスレッドを表示())
            外のスレッドに行く()
            print("終わり。", 今のスレッドを表示())
        }
        .buttonStyle(.borderedProminent)
    }

    func 外のスレッドに行く() {
        DispatchQueue.global().async {
            // このDispatchQueueは外のスレッドに行きたいだけ。
            print("外のスレッド始め。", 今のスレッドを表示())
            iを数える()
            print("外のスレッド終わり。", 今のスレッドを表示())
        }
    }

    func iを数える() {
        for i in 0..<9 {
            DispatchQueue.main.sync {  // ? ここをいろいろ試す。
                sleep(1)
                print(i, 今のスレッドを表示())
            }
        }
    }
    
    func 今のスレッドを表示() -> String {
        return "\(Thread.current)"
    }
    
}

全てメインスレッド(number = 1)で実行された。syncなので外のスレッドとも同期していて「外のスレッド始め。」と「外のスレッド終わり。」の間に実行されている。

始め。 <_NSMainThread: 0x6000009482c0>{number = 1, name = main}
終わり。 <_NSMainThread: 0x6000009482c0>{number = 1, name = main}
外のスレッド始め。 <NSThread: 0x600000908800>{number = 6, name = (null)}
0 <_NSMainThread: 0x6000009482c0>{number = 1, name = main}
1 <_NSMainThread: 0x6000009482c0>{number = 1, name = main}
2 <_NSMainThread: 0x6000009482c0>{number = 1, name = main}
3 <_NSMainThread: 0x6000009482c0>{number = 1, name = main}
4 <_NSMainThread: 0x6000009482c0>{number = 1, name = main}
5 <_NSMainThread: 0x6000009482c0>{number = 1, name = main}
6 <_NSMainThread: 0x6000009482c0>{number = 1, name = main}
7 <_NSMainThread: 0x6000009482c0>{number = 1, name = main}
8 <_NSMainThread: 0x6000009482c0>{number = 1, name = main}
外のスレッド終わり。 <NSThread: 0x600000908800>{number = 6, name = (null)}

DispatchQueue.main.async

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("ボタンをタップ") {
            print("始め。", 今のスレッドを表示())
            外のスレッドに行く()
            print("終わり。", 今のスレッドを表示())
        }
        .buttonStyle(.borderedProminent)
    }

    func 外のスレッドに行く() {
        DispatchQueue.global().async {
            // このDispatchQueueは外のスレッドに行きたいだけ。
            print("外のスレッド始め。", 今のスレッドを表示())
            iを数える()
            print("外のスレッド終わり。", 今のスレッドを表示())
        }
    }

    func iを数える() {
        for i in 0..<9 {
            DispatchQueue.main.async {  // ? ここをいろいろ試す。
                sleep(1)
                print(i, 今のスレッドを表示())
            }
        }
    }
    
    func 今のスレッドを表示() -> String {
        return "\(Thread.current)"
    }
    
}

全てメインスレッド(number = 1)で実行された。asyncなので外のスレッドとは非同期で「外のスレッド終わり。」が先に実行されている。

始め。 <_NSMainThread: 0x600001db4100>{number = 1, name = main}
終わり。 <_NSMainThread: 0x600001db4100>{number = 1, name = main}
外のスレッド始め。 <NSThread: 0x600001ddc180>{number = 6, name = (null)}
外のスレッド終わり。 <NSThread: 0x600001ddc180>{number = 6, name = (null)}
0 <_NSMainThread: 0x600001db4100>{number = 1, name = main}
1 <_NSMainThread: 0x600001db4100>{number = 1, name = main}
2 <_NSMainThread: 0x600001db4100>{number = 1, name = main}
3 <_NSMainThread: 0x600001db4100>{number = 1, name = main}
4 <_NSMainThread: 0x600001db4100>{number = 1, name = main}
5 <_NSMainThread: 0x600001db4100>{number = 1, name = main}
6 <_NSMainThread: 0x600001db4100>{number = 1, name = main}
7 <_NSMainThread: 0x600001db4100>{number = 1, name = main}
8 <_NSMainThread: 0x600001db4100>{number = 1, name = main}

DispatchQueue.global().sync

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("ボタンをタップ") {
            print("始め。", 今のスレッドを表示())
            外のスレッドに行く()
            print("終わり。", 今のスレッドを表示())
        }
        .buttonStyle(.borderedProminent)
    }

    func 外のスレッドに行く() {
        DispatchQueue.global().async {
            // このDispatchQueueは外のスレッドに行きたいだけ。
            print("外のスレッド始め。", 今のスレッドを表示())
            iを数える()
            print("外のスレッド終わり。", 今のスレッドを表示())
        }
    }

    func iを数える() {
        for i in 0..<9 {
            DispatchQueue.global().sync {  // ? ここをいろいろ試す。
                sleep(1)
                print(i, 今のスレッドを表示())
            }
        }
    }
    
    func 今のスレッドを表示() -> String {
        return "\(Thread.current)"
    }
    
}

全て外部スレッド(number = 6)で実行された。syncなので外のスレッドに同期していて「外のスレッド始め。」と「外のスレッド終わり。」の間に実行されている。

始め。 <_NSMainThread: 0x600003810000>{number = 1, name = main}
終わり。 <_NSMainThread: 0x600003810000>{number = 1, name = main}
外のスレッド始め。 <NSThread: 0x600003850a40>{number = 6, name = (null)}
0 <NSThread: 0x600003850a40>{number = 6, name = (null)}
1 <NSThread: 0x600003850a40>{number = 6, name = (null)}
2 <NSThread: 0x600003850a40>{number = 6, name = (null)}
3 <NSThread: 0x600003850a40>{number = 6, name = (null)}
4 <NSThread: 0x600003850a40>{number = 6, name = (null)}
5 <NSThread: 0x600003850a40>{number = 6, name = (null)}
6 <NSThread: 0x600003850a40>{number = 6, name = (null)}
7 <NSThread: 0x600003850a40>{number = 6, name = (null)}
8 <NSThread: 0x600003850a40>{number = 6, name = (null)}
外のスレッド終わり。 <NSThread: 0x600003850a40>{number = 6, name = (null)}

DispatchQueue.global().async

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("ボタンをタップ") {
            print("始め。", 今のスレッドを表示())
            外のスレッドに行く()
            print("終わり。", 今のスレッドを表示())
        }
        .buttonStyle(.borderedProminent)
    }

    func 外のスレッドに行く() {
        DispatchQueue.global().async {
            // このDispatchQueueは外のスレッドに行きたいだけ。
            print("外のスレッド始め。", 今のスレッドを表示())
            iを数える()
            print("外のスレッド終わり。", 今のスレッドを表示())
        }
    }

    func iを数える() {
        for i in 0..<9 {
            DispatchQueue.global().async {  // ? ここをいろいろ試す。
                sleep(1)
                print(i, 今のスレッドを表示())
            }
        }
    }
    
    func 今のスレッドを表示() -> String {
        return "\(Thread.current)"
    }
    
}

別のスレッドが9本立って並列に実行された。

始め。 <_NSMainThread: 0x60000079c540>{number = 1, name = main}
終わり。 <_NSMainThread: 0x60000079c540>{number = 1, name = main}
外のスレッド始め。 <NSThread: 0x6000007c0000>{number = 6, name = (null)}
外のスレッド終わり。 <NSThread: 0x6000007c0000>{number = 6, name = (null)}
5 <NSThread: 0x6000007d1100>{number = 5, name = (null)}
0 <NSThread: 0x6000007f8200>{number = 7, name = (null)}
2 <NSThread: 0x6000007f5d80>{number = 9, name = (null)}
1 <NSThread: 0x600000796a00>{number = 4, name = (null)}
3 <NSThread: 0x6000007d93c0>{number = 3, name = (null)}
4 <NSThread: 0x6000007c0000>{number = 6, name = (null)}
7 <NSThread: 0x6000007faf00>{number = 11, name = (null)}
8 <NSThread: 0x6000007e8600>{number = 12, name = (null)}
6 <NSThread: 0x6000007c8040>{number = 10, name = (null)}

まとめ

SwiftUIのDispatchQueue main/global() sync/asyncの違いを実験で理解した結果を説明した。

コメント

タイトルとURLをコピーしました