I usually just pass lambda for progress. Much less...
# coroutines
g
I usually just pass lambda for progress. Much less overhead than channel. But you can get callback on a different thread, so should handle this case
1
l
That's a great idea, also I can take advantage of lambda default value for the cases where I don't need progress
g
yes, exactly what I do. just use
null
as default value for progress
There are obvious problems with this approach: progress callback can be on any thread, callback can block progress. Channel is more robust solution, but in case of channel it is much more object created on each progress (instead of single lambda)
There is also approach to use channel that emits sealed class progress + result, but also more heavy weight solution So in many cases I ended with lambda for progress
d
I like to launch coroutine with a receiver where you can set the progress. A lot like the lambda approach, but it's nicer
e
You can use suspend callback fun and if your coroutine is in the main thread, you can touch your UI right from the callback (or use
withContext
)
g
problem with suspend callback that it also can suspend and slowdown you operation (same with standard lambda), but it’s fine trade off for me
d
Yeah, instead of
withContext
, would probably use
launch
instead to prevent it waiting
g
This creates new coroutine on each progress, sounds wasteful for me. Not so much
d
Well, alternatively you could have a loop on a delay, launched on UI thread, that updates progress. It would have to be stored in a property somewhere. You probably want to make that volatile.
b
coroutines are very lightweight and you shouldn't worry about creating new ones for that purpose. also one advantage of coroutines is so you don't have to use callbacks, so I would definitely prefer a channel (the 'overhead' is probably negligible compared to the callback method)
g
you shouldn’t worry about creating new ones for that purpose
Yes, it just a couple of objects + work on dispatching of coroutine (that can involve context switching), but it depends on how often you notify progress, it may be that you have a lot of progress notifications and overheads of progress notifications will be visible. Overhead of channel is much higher, on each sent event you pay a few objects and atomic operations And channel is not so different for callback in this case
b
I think you're falling into the trap of premature optimization. I seriously doubt for this use case you'll ever reach enough coroutines to have anything but negligible overhead. Same with channels. By using callbacks you lose some of the structural advantages gained by using coroutines/channels (see https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/).
g
I see overhead of channels in my case and optimized some usages when channel is not so important for very often notifications
enough coroutines to have anything but negligible overhead
And this is not a problem of coroutines, it’s just because channels is thread and concurrently safe abstraction
Again, I don’t have anything against channel, this is abstraction that works for intended use case In some cases you can replace it with something more light weight
e
Btw, same story is in Go. Channels are great when you need them but in simple cases like progress callbacks are just as good.
d
If you really want to use a channel, use a
ConflatedChannel
which will drop older elements and never suspend on
send
calls.
e
👍 Yep, for indicating progress via state update messages (list “current progress is xx%“) it works perfectly and also nicely works under load
d
Yeah exactly, it's important that a more recent message makes reception of an older message obsolete. Which is usually the case with progress updates.