回答編集履歴

2

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

2019/12/28 08:44

投稿

eytyet
eytyet

スコア803

test CHANGED
@@ -127,3 +127,83 @@
127
127
 
128
128
 
129
129
  なお、元のソースで`DispatchGroup`への`enter()`と`leave()`の呼び出しは必要ありません。
130
+
131
+
132
+
133
+ ## 追記
134
+
135
+ 並列度合いを調整するには、オペレーションキューの`maxConcurrentOperationCount`を使うと簡単です。
136
+
137
+ この場合は、キューが処理数を制御できるように、ひとつひとつの処理をそれぞれ別の`BlockOperation`に分割して登録することになります。また、こうすると全処理の完了検知に`BlockOperation`の`completion`を使うことができないので、かわりに、completionの処理を一つの`BlockOperation`として、他の処理が終わったら実行するように設定してオペレーションキューに入れました。
138
+
139
+ このように変更した`doMultiAsyncProcess`は以下のようになると思います。
140
+
141
+
142
+
143
+ ```
144
+
145
+ func doMultiAsyncProcess(code: String) {
146
+
147
+ guard operationQueue == nil else { return }
148
+
149
+
150
+
151
+ operationQueue = OperationQueue()
152
+
153
+ // 並行処理数を3つまでに制限。
154
+
155
+ operationQueue?.maxConcurrentOperationCount = 3
156
+
157
+
158
+
159
+ // 全部が完了したら実行する処理。(キャンセルした時は呼ばれない)
160
+
161
+ let currentQueue = operationQueue
162
+
163
+ let completion = BlockOperation()
164
+
165
+ completion.addExecutionBlock {
166
+
167
+ print("All process for (code) has been done.")
168
+
169
+ if currentQueue == self.operationQueue {
170
+
171
+ self.operationQueue = nil
172
+
173
+ }
174
+
175
+ }
176
+
177
+
178
+
179
+ // 非同期処理をオペレーションキューに登録
180
+
181
+ list.forEach { (value) in
182
+
183
+ var blockOperation = BlockOperation()
184
+
185
+ blockOperation.addExecutionBlock {
186
+
187
+ guard !blockOperation.isCancelled else { return }
188
+
189
+ Thread.sleep(forTimeInterval: Double.random(in: 1..<2))
190
+
191
+ guard !blockOperation.isCancelled else { return }
192
+
193
+ // API通信開始
194
+
195
+ self.getAPIResult(item:value, code: code, operation: blockOperation)
196
+
197
+ }
198
+
199
+ completion.addDependency(blockOperation)
200
+
201
+ operationQueue?.addOperation(blockOperation)
202
+
203
+ }
204
+
205
+ operationQueue?.addOperation(completion)
206
+
207
+ }
208
+
209
+ ```

1

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

2019/12/28 08:44

投稿

eytyet
eytyet

スコア803

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