まず、スレッドとキューの違い、およびGCDが実際に行うことを理解することが重要です。(GCDを介して)ディスパッチキューを使用する場合、スレッドではなく、実際にキューに入れられます。ディスパッチフレームワークは、Appleが「正しいスレッドソリューションを実装することが、不可能ではないにしても、不可能ではないにしても、非常に困難になる可能性がある」ことを認めているため、スレッドから遠ざけるために特別に設計されました。したがって、タスク(UIをフリーズさせたくないタスク)を同時に実行するには、それらのタスクのキューを作成してGCDに渡すだけです。また、GCDは関連するすべてのスレッドを処理します。したがって、実際に行っているのはキューイングだけです。
すぐに知っておくべき2番目のことは、タスクとは何かです。タスクとは、そのキューブロック内のすべてのコードです(キュー内ではなく、常にキューに何かを追加できるため、キューに追加したクロージャ内にあります)。タスクはブロックと呼ばれることもあり、ブロックはタスクと呼ばれることもあります(ただし、特にSwiftコミュニティでは、タスクとして一般的に知られています)。また、コードの量に関係なく、中括弧内のすべてのコードは単一のタスクと見なされます。
serialQueue.async {
// this is one task
// it can be any number of lines with any number of methods
}
serialQueue.async {
// this is another task added to the same queue
// this queue now has two tasks
}
また、同時実行とは単に他のことと同時に実行することを意味し、シリアルとは次々と(同時に実行することはない)ことを意味することは明らかです。何かをシリアル化する、または何かをシリアルに配置するということは、左から右へ、上から下へ、中断することなく、最初から最後までその順序で実行することを意味します。
キューには、シリアルと同時の2つのタイプがありますが、すべてのキューは相互に同時です。「バックグラウンドで」コードを実行したいということは、別のスレッド(通常はメインスレッド)と同時にコードを実行したいということです。したがって、すべてのディスパッチキューは、シリアルまたは同時で、他のキューと比較してタスクを同時に実行します。キューによって(シリアルキューによって)実行されるシリアル化は、その単一の[シリアル]ディスパッチキュー内のタスクにのみ関係します(同じシリアルキュー内に2つのタスクがある上記の例のように、これらのタスクは後で実行されます)もう一方は、決して同時には行いません)。
シリアルキュー(プライベートディスパッチキューとも呼ばれます)は、特定のキューに追加された順序で、開始から終了まで一度に1つずつタスクの実行を保証します。これは、ディスパッチキューの説明のどこかでのシリアル化の唯一の保証です。-特定のシリアルキュー内の特定のタスクがシリアルで実行されること。ただし、繰り返しますが、すべてのキューが相互に並行しているため、シリアルキューは、別のキューである場合、他のシリアルキューと同時に実行できます。すべてのタスクは個別のスレッドで実行されますが、すべてのタスクが同じスレッドで実行されることが保証されているわけではありません(重要ではありませんが、知っておくと興味深いでしょう)。また、iOSフレームワークにはすぐに使用できるシリアルキューは付属していません。作成する必要があります。プライベート(非グローバル)キューはデフォルトでシリアルであるため、シリアルキューを作成するには:
let serialQueue = DispatchQueue(label: "serial")
属性プロパティを使用して、コンカレントにすることができます。
let concurrentQueue = DispatchQueue(label: "concurrent", attributes: [.concurrent])
ただし、この時点で、プライベートキューに他の属性を追加しない場合は、すぐに使用できるグローバルキュー(すべて同時)の1つを使用することをお勧めします。この回答の下部には、(targetプロパティを使用して)シリアルキューを作成する別の方法が表示されます。これは、Appleが(より効率的なリソース管理のために)実行することをお勧めする方法です。しかし、現時点では、ラベルを付けるだけで十分です。
CONCURRENT QUEUES(多くの場合、グローバルディスパッチキュー)は、タスクを同時に実行できます。ただし、タスクは特定のキューに追加された順序で開始することが保証されていますが、シリアルキューとは異なり、キューは最初のタスクが完了するのを待たずに2番目のタスクを開始します。タスク(シリアルキューと同様)は個別のスレッドで実行され、(シリアルキューと同様)すべてのタスクが同じスレッドで実行されることが保証されているわけではありません(重要ではありませんが、知っておくと興味深いでしょう)。また、iOSフレームワークには、すぐに使用できる4つの同時キューが付属しています。上記の例を使用するか、Appleのグローバルキューのいずれかを使用して、コンカレントキューを作成できます(通常はこれをお勧めします)。
let concurrentQueue = DispatchQueue.global(qos: .default)
RETAIN-CYCLE RESISTANT:ディスパッチキューは参照カウントオブジェクトですが、グローバルキューはグローバルであるため保持および解放する必要はなく、保持および解放は無視されます。プロパティに割り当てる必要なく、グローバルキューに直接アクセスできます。
キューをディスパッチするには、同期と非同期の2つの方法があります。
SYNC DISPATCHINGは、キューがディスパッチされたスレッド(呼び出しスレッド)が、キューのディスパッチ後に一時停止し、再開する前にそのキューブロック内のタスクの実行が完了するのを待機することを意味します。同期的にディスパッチするには:
DispatchQueue.global(qos: .default).sync {
// task goes in here
}
ASYNC DISPATCHINGは、呼び出しスレッドがキューのディスパッチ後も実行を続け、そのキューブロック内のタスクの実行が完了するのを待たないことを意味します。非同期でディスパッチするには:
DispatchQueue.global(qos: .default).async {
// task goes in here
}
タスクをシリアルで実行するにはシリアルキューを使用する必要があると考えるかもしれませんが、それは正確ではありません。複数のタスクをシリアルで実行するには、シリアルキューを使用する必要がありますが、すべてのタスク(それ自体で分離)はシリアルで実行されます。この例を考えてみましょう:
whichQueueShouldIUse.syncOrAsync {
for i in 1...10 {
print(i)
}
for i in 1...10 {
print(i + 100)
}
for i in 1...10 {
print(i + 1000)
}
}
このキューをどのように構成(シリアルまたはコンカレント)またはディスパッチ(同期または非同期)しても、このタスクは常にシリアルで実行されます。3番目のループは2番目のループの前に実行されることはなく、2番目のループは最初のループの前に実行されることはありません。これは、ディスパッチを使用するすべてのキューに当てはまります。複数のタスクやキューを導入するときに、シリアルと同時実行が実際に役立ちます。
次の2つのキューについて考えてみましょう。1つはシリアル、もう1つは同時です。
let serialQueue = DispatchQueue(label: "serial")
let concurrentQueue = DispatchQueue.global(qos: .default)
2つの同時キューを非同期でディスパッチするとします。
concurrentQueue.async {
for i in 1...5 {
print(i)
}
}
concurrentQueue.async {
for i in 1...5 {
print(i + 100)
}
}
1
101
2
102
103
3
104
4
105
5
それらの出力は(予想どおり)乱雑ですが、各キューが独自のタスクをシリアルに実行したことに注意してください。これは同時実行性の最も基本的な例です。2つのタスクが同じキューのバックグラウンドで同時に実行されます。では、最初のシリアルを作成しましょう:
serialQueue.async {
for i in 1...5 {
print(i)
}
}
concurrentQueue.async {
for i in 1...5 {
print(i + 100)
}
}
101
1
2
102
3
103
4
104
5
105
最初のキューはシリアルで実行されることになっていますか?それは(そして2番目もそうでした)。バックグラウンドで他に何が起こったとしても、キューには関係ありません。シリアルキューにシリアルで実行するように指示したところ、実際に実行されましたが、タスクは1つだけです。次に、2つのタスクを割り当てます。
serialQueue.async {
for i in 1...5 {
print(i)
}
}
serialQueue.async {
for i in 1...5 {
print(i + 100)
}
}
1
2
3
4
5
101
102
103
104
105
そして、これは最も基本的な(そして唯一可能な)シリアライゼーションの例です-同じキュー内の(メインスレッドへの)バックグラウンドで(順番に)シリアルで実行される2つのタスクです。しかし、それらを2つの個別のシリアルキューにした場合(上記の例ではそれらは同じキューであるため)、それらの出力は再びごちゃ混ぜになります。
serialQueue.async {
for i in 1...5 {
print(i)
}
}
serialQueue2.async {
for i in 1...5 {
print(i + 100)
}
}
1
101
2
102
3
103
4
104
5
105
そして、これは、すべてのキューが相互に同時であると言ったときに私が言ったことです。これらは、同時にタスクを実行する2つのシリアルキューです(別々のキューであるため)。キューは他のキューを知らないか、気にしません。ここで、(同じキューの)2つのシリアルキューに戻り、3つ目のキューを同時に追加します。
serialQueue.async {
for i in 1...5 {
print(i)
}
}
serialQueue.async {
for i in 1...5 {
print(i + 100)
}
}
concurrentQueue.async {
for i in 1...5 {
print(i + 1000)
}
}
1
2
3
4
5
101
102
103
104
105
1001
1002
1003
1004
1005
それは一種の予期しないことですが、並行キューが実行される前にシリアルキューが完了するのを待機したのはなぜですか?それは並行性ではありません。あなたの遊び場は異なる出力を表示するかもしれませんが、私のものはこれを示しました。そして、これは、私の並行キューの優先度がGCDがタスクをより早く実行するのに十分に高くなかったために、これを示しました。したがって、すべてを同じに保ちながらグローバルキューのQoS(キューの優先度レベルであるサービスの品質)を変更するとlet concurrentQueue = DispatchQueue.global(qos: .userInteractive)
、出力は期待どおりになります。
1
1001
1002
1003
2
1004
1005
3
4
5
101
102
103
104
105
2つのシリアルキューはタスクを(期待どおりに)シリアルで実行し、コンカレントキューは高い優先度(高いQoS、またはサービス品質)が与えられたため、タスクをより速く実行しました。
最初の印刷例のように、2つの同時キューは、(予想どおり)乱雑な印刷を示します。それらをシリアルできれいに印刷するには、両方を同じシリアルキュー(同じラベルだけでなく、そのキューの同じインスタンス)にする必要があります。次に、各タスクが他のタスクに対して順次実行されます。ただし、それらをシリアルで印刷させる別の方法は、両方を同時に維持するが、ディスパッチ方法を変更することです。
concurrentQueue.sync {
for i in 1...5 {
print(i)
}
}
concurrentQueue.async {
for i in 1...5 {
print(i + 100)
}
}
1
2
3
4
5
101
102
103
104
105
同期ディスパッチとは、キュー内のタスクが完了するまで呼び出しスレッドが待機してから続行することを意味します。ここでの警告は、明らかに、最初のタスクが完了するまで呼び出しスレッドが凍結されることです。これは、UIの実行方法とは異なる場合があります。
このため、次のことはできません。
DispatchQueue.main.sync { ... }
これは、実行できないキューとディスパッチングメソッドの唯一の可能な組み合わせ、つまりメインキューでの同期ディスパッチです。これは、中かっこ内でタスクを実行するまでメインキューをフリーズするように要求しているためです。フリーズしたメインキューにディスパッチしました。これはデッドロックと呼ばれます。遊び場で実際にそれを見るには:
DispatchQueue.main.sync { // stop the main queue and wait for the following to finish
print("hello world") // this will never execute on the main queue because we just stopped it
}
// deadlock
最後に言及すべきことは、リソースです。キューにタスクを与えると、GCDはその内部管理プールから使用可能なキューを見つけます。この回答の記述に関する限り、QoSごとに64のキューが利用可能です。それは多くのように思えるかもしれませんが、特にサードパーティのライブラリ、特にデータベースフレームワークによって、すぐに消費される可能性があります。このため、Appleはキュー管理に関する推奨事項を持っています(以下のリンクに記載されています)。1つは:
プライベート並行キューを作成する代わりに、グローバル並行ディスパッチキューの1つにタスクを送信します。シリアルタスクの場合、シリアルキューのターゲットをグローバルコンカレントキューの1つに設定します。
これにより、スレッドを作成する個別のキューの数を最小限に抑えながら、キューのシリアル化された動作を維持できます。
これを行うには、以前に行ったように(まだ可能です)作成するのではなく、次のようなシリアルキューを作成することをお勧めします。
let serialQueue = DispatchQueue(label: "serialQueue", qos: .default, attributes: [], autoreleaseFrequency: .inherit, target: .global(qos: .default))
詳細については、以下をお勧めします。
https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091-CH1-SW1
https://developer.apple.com/documentation/dispatch/dispatchqueue