質問するログイン新規登録

回答編集履歴

2

並行処理の制限方法を追記

2019/12/28 08:44

投稿

eytyet
eytyet

スコア803

answer CHANGED
@@ -62,4 +62,44 @@
62
62
 
63
63
  多分こんな感じでいいと思いますが、問題あれば教えてください。
64
64
 
65
- なお、元のソースで`DispatchGroup`への`enter()`と`leave()`の呼び出しは必要ありません。
65
+ なお、元のソースで`DispatchGroup`への`enter()`と`leave()`の呼び出しは必要ありません。
66
+
67
+ ## 追記
68
+ 並列度合いを調整するには、オペレーションキューの`maxConcurrentOperationCount`を使うと簡単です。
69
+ この場合は、キューが処理数を制御できるように、ひとつひとつの処理をそれぞれ別の`BlockOperation`に分割して登録することになります。また、こうすると全処理の完了検知に`BlockOperation`の`completion`を使うことができないので、かわりに、completionの処理を一つの`BlockOperation`として、他の処理が終わったら実行するように設定してオペレーションキューに入れました。
70
+ このように変更した`doMultiAsyncProcess`は以下のようになると思います。
71
+
72
+ ```
73
+ func doMultiAsyncProcess(code: String) {
74
+ guard operationQueue == nil else { return }
75
+
76
+ operationQueue = OperationQueue()
77
+ // 並行処理数を3つまでに制限。
78
+ operationQueue?.maxConcurrentOperationCount = 3
79
+
80
+ // 全部が完了したら実行する処理。(キャンセルした時は呼ばれない)
81
+ let currentQueue = operationQueue
82
+ let completion = BlockOperation()
83
+ completion.addExecutionBlock {
84
+ print("All process for (code) has been done.")
85
+ if currentQueue == self.operationQueue {
86
+ self.operationQueue = nil
87
+ }
88
+ }
89
+
90
+ // 非同期処理をオペレーションキューに登録
91
+ list.forEach { (value) in
92
+ var blockOperation = BlockOperation()
93
+ blockOperation.addExecutionBlock {
94
+ guard !blockOperation.isCancelled else { return }
95
+ Thread.sleep(forTimeInterval: Double.random(in: 1..<2))
96
+ guard !blockOperation.isCancelled else { return }
97
+ // API通信開始
98
+ self.getAPIResult(item:value, code: code, operation: blockOperation)
99
+ }
100
+ completion.addDependency(blockOperation)
101
+ operationQueue?.addOperation(blockOperation)
102
+ }
103
+ operationQueue?.addOperation(completion)
104
+ }
105
+ ```

1

回答を処理のキャンセル例に変更しました。

2019/12/28 08:44

投稿

eytyet
eytyet

スコア803

answer CHANGED
@@ -1,10 +1,65 @@
1
+ オペレーションキューとブロックオペレーションを使って次のようにするのはいかがでしょうか。
1
- とめられないの既に実行済みだからです。
2
+ (`list`の情報がないの一旦`Int`の配列実装しています。)
2
3
 
3
- `forEach`の内部の`dispatchQueue.async`は、実行すると即座終了ます。しがって、`forEach`自体もすぐ終わっています。通信を待っていないので、ここでキャンセルさことはできません
4
+ `BlockOperation`にした処理で、キャンセル状態を監視してキャンセルれていたら自らやめようにすれば処理のキャンセルを実現できま
4
5
 
6
+ ```swift
5
- なお、渡したクロージャはあとで別スレッドで実行されます。並列キューに入れているので、この`forEach`ではlistの数だけスレッドをたてて、それぞれにAPI通信をするクロージャを渡しています。つまり、全部一度に通信を始めるように設定しています。
7
+ let list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
6
8
 
7
- DispatchGroupで順番を制御しようとしているのでしょうか。
9
+ private var operationQueue: OperationQueue?
8
- 実際には1秒後に全部の通信が同時に発生していると思いますが、どうでしょうか。
9
10
 
11
+ /// 複数の処理を並列に実行する
12
+ func doMultiAsyncProcess(code: String) {
13
+ guard operationQueue == nil else { return }
14
+
15
+ operationQueue = OperationQueue()
16
+
17
+ let blockOperation = BlockOperation()
18
+ let currentQueue = operationQueue
19
+ blockOperation.completionBlock = {
20
+ // 全部完了したときの処理。キャンセルした場合も呼ばれる。
21
+ print("All process for (code) has been done.")
22
+ // キャンセルされて次の処理が始まっていたら違うキューに変わっている
23
+ if currentQueue == self.operationQueue {
24
+ self.operationQueue = nil
25
+ }
26
+ }
27
+
28
+ // 非同期処理をオペレーションに登録
29
+ list.forEach { (value) in
30
+ blockOperation.addExecutionBlock {
31
+ guard !blockOperation.isCancelled else { return }
32
+ Thread.sleep(forTimeInterval: Double.random(in: 1..<2))
33
+ guard !blockOperation.isCancelled else { return }
34
+ // API通信開始
35
+ self.getAPIResult(item:value, code: code, operation: blockOperation)
36
+ }
37
+ }
38
+
39
+ // 実行開始
40
+ operationQueue?.addOperation(blockOperation)
41
+ }
42
+
43
+ /// 実行中の処理をキャンセルする。
44
+ func cancellMultiAsyncProcess() {
45
+ guard let queue = operationQueue else { return }
46
+ print("Request cancell of the operations.")
47
+ queue.cancelAllOperations()
48
+ operationQueue = nil
49
+ }
50
+
10
- 順番やりつつキャンセルをしたいなら、dispatchQueueを直列キューにして、それぞれのクロージャーの中でキャンセルするべきかどうかを判断するようするとよと思います。
51
+ /// サーバーと通信する。ダミーで単ウェイトて通信しする処理としています。また、キャンセルで中断するためオペレーションも伝えています。
52
+ func getAPIResult(item: Int, code: String, operation: Operation) {
53
+ print("getAPIResult(item:(item), code:(code) is started.")
54
+ Thread.sleep(forTimeInterval: Double.random(in: 1..<5))
55
+ if operation.isCancelled {
56
+ print("getAPIResult(item:(item), code:(code) Cancelled.")
57
+ } else {
58
+ print("getAPIResult(item:(item), code:(code) received a result.")
59
+ }
60
+ }
61
+ ```
62
+
63
+ 多分こんな感じでいいと思いますが、問題あれば教えてください。
64
+
65
+ なお、元のソースで`DispatchGroup`への`enter()`と`leave()`の呼び出しは必要ありません。