You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -24,7 +24,19 @@ suspend fun suspendingFunction() : Int {
24
24
{% endhighlight %}
25
25
26
26
## Kontekst
27
-
Kontekst współprogramu (`CoroutineContext`) jest zbiorem zasad i konfiguracji, która definiuje sposób w jaki coroutine będzie wykonywany (może być kombinacją różnych kontekstów `CombinedContext`). Jednym z możliwych sposobów dostarczenia kontekstu jest użycie klasy `Dispatchers` zawierającej zbiór implementacji różniących się wykorzystaniem wątków. `Dispatchers.Default` może działać na wielu wątkach i używany jest do kosztownych zadań o dużym zapotrzebowaniu mocy obliczeniowej jak np. algorytmy. `Dispatchers.Main` działa na głównym wątku co w przypadku Android pozwala na modyfikację interfejsu użytkownika. `Dispatchers.IO` jest używany przede wszystkim do prostych operacji typu wejście/wyjście jak np. zapytanie sieciowe, dostęp do bazy danych, plików czy sensorów. `Dispatchers.Unconfined` nie ogranicza się do żadnego wątku w wyniku czego jego zachowanie jest trudne do przewidzenia. Kontekst może być przekazany jawnie jako argument lub uzyskiwany niejawnie na podstawie zakresu w którym jest wykonywany.
27
+
Kontekst współprogramu (`CoroutineContext`) jest zbiorem zasad i konfiguracji, która definiuje sposób w jaki coroutine będzie wykonywany. Może być także kombinacją obiektów różnych typów kontekstu (`CombinedContext`). Składa się przeważnie z obiektów typu `Job`, `CoroutineDispatcher` oraz `CoroutineExceptionHandler`. Coroutine zawsze wykonywany jest w ramach jakiegoś kontekstu, który może być przekazany jawnie jako argument lub uzyskiwany niejawnie na podstawie zakresu w którym jest wykonywany.
28
+
29
+
{% highlight kotlin %}
30
+
val handler = CoroutineExceptionHandler { context, exception ->
31
+
//manage caught exception like logger action
32
+
}
33
+
34
+
//actually this is CombinedContext object
35
+
val context : CoroutineContext = Dispatchers.Default + Job() + handler
36
+
{% endhighlight %}
37
+
38
+
## Dispatcher
39
+
Kontekts zawiera m.in. instancję `CoroutineDispatcher`, której zadaniem jest określenie wątków wykonawczych dla coroutine. `Dispatchers.Default` może działać na wielu wątkach i używany jest do kosztownych zadań o dużym zapotrzebowaniu mocy obliczeniowej jak np. algorytmy. `Dispatchers.Main` działa na głównym wątku co w przypadku Android pozwala na modyfikację interfejsu użytkownika. `Dispatchers.IO` jest używany przede wszystkim do prostych operacji typu wejście/wyjście jak np. zapytanie sieciowe, dostęp do bazy danych, plików czy sensorów. `Dispatchers.Unconfined` nie ogranicza się do żadnego wątku w wyniku czego jego zachowanie jest trudne do przewidzenia.
28
40
29
41
{% highlight kotlin %}
30
42
//withContext is suspend functions itself so no need to declare in inside suspend function if used inside coroutine
@@ -52,31 +64,30 @@ fun testSuspendingWork() = runBlocking {
52
64
`launch` jest często wykorzystywanym budowniczym, który w przeciwieństwie do `runBlocking` nie blokuje bieżącego wątku. Zwraca obiekt typu `Job` dzięki któremu możliwe jest manualne zarządzanie stanem zadań. Metoda `join` blokuje powiązany coroutine tak długo dopóki wszystkie jego zadania nie zostaną wykonane, natomiast `cancel` anuluje wszystkie zadania. Wykorzystywany do zadań typu `fire and forget` w których nie oczekuje się zwrócenia rezultatu.
53
65
54
66
{% highlight kotlin %}
55
-
//coroutines must be called on some scope so just use main scope called GlobalScope
56
67
suspend fun launchJobAndJoin() {
57
68
//Job extends CoroutineContext to it's context itself
58
-
val job = GlobalScope.launch(Dispatchers.Main) {
69
+
val job = launch { //note that coroutines must be called on some scope
59
70
//some work
60
71
val result1 = suspendingWork1()
61
72
//wait for result1
62
73
val result2 = suspendingWork2()
63
74
//wait for result2
64
75
//process results
65
76
}
66
-
67
-
//wait here until suspendingWork tasks finished
77
+
78
+
//wait here until child tasks finished
68
79
job.join() //suspending function itself
69
80
}
70
81
71
82
fun launchJobAndCancel() {
72
-
val job = GlobalScope.launch(Dispatchers.Main) {
83
+
val job = launch {
73
84
//some work
74
85
val result1 = suspendingWork1()
75
86
val result2 = suspendingWork2()
76
87
//process results
77
88
}
78
-
79
-
//cancel all the jobs, so if suspendingWork is running then cancel it and pending tasks
89
+
90
+
//cancel all cancellable jobs, so if suspendingWork is running then cancel it and pending tasks
80
91
job.cancel() //regular function so no need to run inside coroutine or suspend function
81
92
}
82
93
{% endhighlight %}
@@ -85,16 +96,21 @@ fun launchJobAndCancel() {
85
96
86
97
{% highlight kotlin %}
87
98
fun launchAsync() {
88
-
val job = GlobalScope.launch(Dispatchers.Main) {
99
+
val job = launch {
89
100
val result = suspendingWork()
101
+
90
102
val deferred1 = async {
91
103
//some work
92
104
return@async "result2"
93
105
}
94
-
val deferred2 = async {
106
+
107
+
//async can be lazy started when manual start or await called
108
+
val deferred2 = async(start = CoroutineStart.LAZY) {
95
109
//some work
96
110
return@async "result3"
97
111
}
112
+
//note that if no start for lazy async called then behaviour is sequantial when await called
113
+
deferred2.start()
98
114
99
115
//if in this place deferred1 and deferred2 not finished, wait for it
100
116
val finalResult = "$result ${deferred1.await()} ${deferred2.await()}"
@@ -103,6 +119,71 @@ fun launchAsync() {
103
119
}
104
120
{% endhighlight %}
105
121
122
+
## Anulowanie
123
+
Wszystkie funkcje zawieszenia w coroutine są `cancellable`, tzn. potrafią obsłużyć żądanie o anulowaniu pracy przez metodę `cancel`. Jeśli coroutine został anulowany to wyrzucany jest wyjątek `CancellationException` w wyniku czego następuje przerwanie działania. Jednakże jeśli blok kodu nie jest elementem funkcji zawieszenia wówczas nie dochodzi do automatycznego sprawdzania stanu pracy co sprawia, że kod nie reaguje na żądanie `cancel`. W takiej sytuacji należy ręcznie sprawdzać stan pracy poprzez właściwość `isActive` lub funkcję `yield` (okresowo zawiesza działanie funkcji) czy też ustawienie maksymalnego czasu wykonania za pomocą funkcji `withTimeout` i `withTimeoutOrNull`.
124
+
125
+
{% highlight kotlin %}
126
+
fun cancellableSuspsend() = runBlocking {
127
+
val job = launch(Dispatchers.Default) {
128
+
repeat(100) {
129
+
//this computation are suspend function, so it is cancellable
130
+
suspendingWork()
131
+
}
132
+
}
133
+
134
+
delay(1) //allow to start coroutine before cancel
135
+
job.cancel()
136
+
}
137
+
138
+
fun notCancellableComputation() = runBlocking {
139
+
val job = launch(Dispatchers.Default) {
140
+
repeat(100) {
141
+
//some intensive computation
142
+
notSuspendingWork()
143
+
}
144
+
}
145
+
146
+
delay(1) //allow to start coroutine before cancel
147
+
job.cancel() //this won't work
148
+
}
149
+
150
+
fun cancellableComputation() = runBlocking {
151
+
val job = launch(Dispatchers.Default) {
152
+
//cancellable code throw CancellationException on cancel
153
+
try {
154
+
repeat(100) {
155
+
//check periodically is scope active or has been cancelled
156
+
if (isActive) {
157
+
//do some intensive computation if coroutine is still active
158
+
notSuspendingWork()
159
+
}
160
+
}
161
+
}
162
+
finally {
163
+
//coroutine has been cancelled
164
+
//run suspending function here will throw CancellationException
165
+
withContext(NonCancellable) {
166
+
//running suspending function is now possible
167
+
}
168
+
}
169
+
}
170
+
171
+
delay(1) //allow to start coroutine before cancel
172
+
job.cancel()
173
+
}
174
+
175
+
//coroutines must be called on some scope so just use main scope called GlobalScope
176
+
fun cancellableByTimeout() = runBlocking {
177
+
//cancel when coroutine couldn't complete after 1 second
178
+
val result = withTimeoutOrNull(1000) {
179
+
repeat(100) {
180
+
notSuspendingWork()
181
+
}
182
+
}
183
+
//use withTime to do the same but throw TimeoutCancellationException instead of return null
184
+
}
185
+
{% endhighlight %}
186
+
106
187
## Zakres
107
188
Coroutines działają w ramach zakresu (`scope`), który stanowi dla nich przestrzeń wykonawczą i jest realizacją struktury hierarchii. Odwołanie się do zakresu wpływa na wszystkie znajdujące się w nim coroutines. Ich wykorzystanie eliminuje problem manualnego zarządzania stanem zadań, których praca i oczekiwanie na rezultat nierzadko ma sens tylko dla bieżącego ekranu (np. ładowanie danych do wyświetlenia). Zamiast ręcznego anulowania wszystkich coroutines wystarczy odwołać je poprzez zakres. Dowolna klasa implementująca `CoroutineScope` oraz nadpisująca właściwość `coroutineContext` może stać się zakresem. Warto zauważyć, że funkcje budowniczego są funkcjami rozszerzającymi `CoroutineScope`. Przykładem zakresu może być `GlobalScope` stanowiący ogólny zakres aplikacji.
108
189
@@ -118,14 +199,13 @@ class ScopeActivity : AppCompatActivity() {
118
199
CoroutineScope(Dispatchers.Main).launch {
119
200
//work
120
201
}
121
-
122
-
val job = GlobalScope.launch(Dispatchers.Main) {
202
+
203
+
val job = GlobalScope.launch {
123
204
//work
124
205
async {
125
206
//coroutine nested in coroutine
126
207
}
127
208
}
128
-
//job.cancel() will cancel parent and childs
129
209
}
130
210
}
131
211
@@ -139,12 +219,12 @@ class ScopeClassActivity : AppCompatActivity(), CoroutineScope {
139
219
override fun onCreate(savedInstanceState: Bundle?) {
140
220
super.onCreate(savedInstanceState)
141
221
142
-
//now calling launch { } is possible because Activity is CoroutineScope itself
222
+
//now calling launch is possible because Activity is CoroutineScope itself
143
223
launch {
144
224
//work
145
225
}
146
226
}
147
-
227
+
148
228
override fun onDestroy() {
149
229
super.onDestroy()
150
230
cancel() //cancel on scope so all coroutines inside scope are cancelling
@@ -157,39 +237,39 @@ class ScopeDelegateActivity : AppCompatActivity(), CoroutineScope by MainScope()
157
237
override fun onCreate(savedInstanceState: Bundle?) {
158
238
super.onCreate(savedInstanceState)
159
239
}
160
-
240
+
161
241
//this class is CoroutineScope itself
162
242
}
163
243
{% endhighlight %}
164
244
165
245
## Kanały
166
-
Kanały są mechanizmem (podobnym do kolejki) pozwalającymi na przesyłanie i odbieranie potoku strumienia wartości między coroutines. W celu zbudowania kanału należy stworzyć instancję klasy `Channel`, która implementuje interfejsy zachowania zarówno nadawcy (`SendChannel`) jak i odbiorcy (`ReceiveChannel`). Opcjonalny parametr przekazany do metody wytwórczej odpowiada za wielkość bufora. Funkcja `send`i `receive`są funkcjami zawieszenia (zawieszają się w przypadku braku odbiorcy lub braku emisji) i umożliwiają odpowiednio emisję i odbiór wartości. Przetwarzanie wartości może także odbywać się poprzez iteracje kanału lub funkcje `consume`, `consumeEach`.
246
+
Kanały są mechanizmem (podobnym do kolejki) pozwalającymi na przesyłanie i odbieranie potoku strumienia wartości między coroutines. W celu zbudowania kanału należy stworzyć instancję klasy `Channel`, która implementuje interfejsy zachowania zarówno nadawcy (`SendChannel`) jak i odbiorcy (`ReceiveChannel`). Opcjonalny parametr przekazany do metody wytwórczej odpowiada za wielkość bufora. Kanały bez buforowe przesyłają elementy dopiero wtedy gdy nadawca i odbiorca są gotowi do komunikacji (spotykają się), tzn. funkcja `send`oczekuje na odbiorcę aby dokonać emisji natomiast funkcja `receive`oczekuje na nadawcę aby rozpocząć odbieranie. W przypadku kanałów buforowych strategia zawieszenia pozwala na wcześniejszą emisję w zależności od wielkościu bufora. Przetwarzanie wartości może także odbywać się poprzez iteracje kanału lub funkcje `consume`, `consumeEach`.
167
247
168
248
{% highlight kotlin %}
169
249
fun runChannel() = runBlocking {
170
250
//create channel using factory method
171
251
//use one of RENDEZVOUS, UNLIMITED, CONFLATED optional param to specify buffer
172
-
val channel = Channel<Int>(RENDEZVOUS)
173
-
252
+
val channel = Channel<Int>(RENDEZVOUS) //it is unbuffered channel
253
+
174
254
launch {
175
255
repeat(3) {
176
256
//send some items from this coroutine
177
257
channel.send(it)
178
258
//suspend if no receivers
179
259
}
180
-
260
+
181
261
//close channel to stop emission, this guarantees to send pending items
182
262
channel.close()
183
263
}
184
-
264
+
185
265
//receive emitted values by receive function
186
266
val value = channel.receive()
187
267
188
268
//as alternative use channel's loop or consume by extension function of ReceiveChannel
189
269
channel.consumeEach {
190
270
//do something with received values: 1, 2, 3
191
271
}
192
-
272
+
193
273
//emitted items can be consumed single time, so trying to receive again will no result
194
274
}
195
275
{% endhighlight %}
@@ -199,22 +279,22 @@ Kanały typu `Channel` ograniczone są do jednorazowego przepływu informacji, t
199
279
{% highlight kotlin %}
200
280
fun runBroadcastChannel() = runBlocking {
201
281
val channel = BroadcastChannel<Int>(UNLIMITED)
202
-
launch {
282
+
launch {
203
283
repeat(3) { channel.send(it) } //doesn't supsend if no receivers
204
284
channel.close()
205
285
}
206
-
286
+
207
287
//items can be consumed by multiple receivers
208
288
channel.consumeEach {}
209
-
channel.consumeEach {}
289
+
channel.consumeEach {}
210
290
}
211
291
{% endhighlight %}
212
292
213
293
Możliwe jest także tworzenie coroutine z automatycznie załączonym kanałem nastawionym na emisje lub odbiór za pomocą funkcji budowniczych producenta i aktora. `produce` uruchamia coroutine, który emituje strumień danych do kanału (jeden nadawca, wielu odbiorców). Zwraca `ReceiveChannel` oraz należy do zakresu `ProducerScope`.
214
294
215
295
{% highlight kotlin %}
216
296
//use it as some variable in real world
217
-
fun runProducer() = GlobalScope.launch {
297
+
fun runProducer() = launch {
218
298
//produce is extension function of CoroutineScope
219
299
val producer : ReceiveChannel<Int> = produce {
220
300
repeat(3) { send(it) }
@@ -223,22 +303,56 @@ fun runProducer() = GlobalScope.launch {
223
303
//use instance of ReceiveChannel to receive values emitted by produce
224
304
producer.consumeEach {
225
305
//do something
226
-
}
306
+
}
227
307
}
228
308
{% endhighlight %}
229
309
230
310
`actor` uruchamia coroutine, który odbiera wiadomości z kanału (jeden odbiorca, wielu nadawców). Zwraca `SendChannel` i należy do zakresu `ActorScope`.
231
311
232
312
{% highlight kotlin %}
233
313
//use it as some variable in real world
234
-
fun runActor() = GlobalScope.launch {
314
+
fun runActor() = launch {
235
315
//actor is extension function of CoroutineScope
236
316
val actor : SendChannel<Int> = actor {
237
317
for(item in channel) {
238
-
//do something
318
+
//often used with selead class to do something
239
319
}
240
320
}
241
321
242
322
repeat(3) { actor.send(it) }
243
323
}
324
+
{% endhighlight %}
325
+
326
+
Wyrażenie `select` umożliwia jednoczesne oczekiwanie na wiele funkcji zawieszenia i wybranie pierwszej, która stanie się dostępna. Metoda `onReceive` definiuje zachowania otrzymania wiadomości przez kanał, natomiast `onSend` dokonuje emisji wartości do kanału.
327
+
328
+
{% highlight kotlin %}
329
+
fun runSelect() = launch {
330
+
val producer = produce {
331
+
repeat(5) { send(it) }
332
+
}
333
+
//more producers
334
+
335
+
val actor = actor<Int> {
336
+
consumeEach {
337
+
//do something
338
+
}
339
+
}
340
+
//more actors
341
+
342
+
//select works on some channels
343
+
repeat(5) {
344
+
select<Unit> {
345
+
//do only one action for first available supsend fun
346
+
347
+
//imagine the case when select must do receive action for multiple channels
0 commit comments