Mark
05/29/2019, 6:31 AMsuspend fun File.trackLength(fileLengthCallback: (Long) -> Unit, refreshInterval: Long = 100L, block: suspend () -> Unit) {
val deferred = GlobalScope.async {
block()
}
while (deferred.isActive) {
fileLengthCallback(length())
delay(refreshInterval)
}
fileLengthCallback(length())
}
gildor
05/29/2019, 6:34 AMcoroutineScope
function to create new scope that attached to this suspend function and start async insidelouiscad
05/29/2019, 6:36 AMasync
if you don't call await
on it also?Mark
05/29/2019, 6:36 AMlouiscad
05/29/2019, 6:37 AMJob
from launch
is enough.coroutineScope { … }
in place of GlobalScope
as Andrey said, so you don't break structured concurrency and don't leak the child coroutine.Mark
05/29/2019, 6:50 AMsuspend fun File.trackLength(fileLengthCallback: (Long) -> Unit, refreshInterval: Long = 100L, block: suspend () -> Unit) {
coroutineScope {
val job = launch {
block()
}
while (job.isActive) {
fileLengthCallback(length())
delay(refreshInterval)
}
}
fileLengthCallback(length())
}
Better?louiscad
05/29/2019, 6:51 AMblock
gildor
05/29/2019, 6:53 AMMark
05/29/2019, 6:53 AMgildor
05/29/2019, 6:54 AMsuspend fun File.trackLength(fileLengthCallback: (Long) -> Unit, refreshInterval: Long = 100L) {
while (true) {
fileLengthCallback(length())
delay(refreshInterval)
}
}
fileLengthCallback(length())
}
Mark
05/29/2019, 6:55 AMgildor
05/29/2019, 6:56 AMMark
05/29/2019, 6:56 AMgildor
05/29/2019, 6:56 AMMark
05/29/2019, 6:57 AMsuspend fun <T> (suspend (File) -> T).invokeAndTrackFileLength(file: File, refreshInterval: Long = 200L, callback: (Long) -> Unit) = withContext(Dispatchers.Main) {
return@withContext coroutineScope {
val deferred = async {
invoke(file)
}
while (deferred.isActive) {
callback(file.length())
delay(refreshInterval)
}
callback(file.length())
deferred.await()
}
}
i.e. an extension function on an existing suspending function providing an alternative to invoke(). Also back to using async
so that we can return the result of the invoke
gildor
05/29/2019, 7:39 AMMark
05/29/2019, 7:42 AMawait
? I vaguely remember something about the async not being invoked until await is called???gildor
05/29/2019, 7:43 AMMark
05/29/2019, 7:43 AMsuspend fun <R> (suspend (File) -> R).invokeAndTrackFileLength(file: File, refreshInterval: Long = 200L, callback: (Long) -> Unit): R = withContext(Dispatchers.Main) {
val deferred = async {
invoke(file)
}
while (deferred.isActive) {
callback(file.length())
delay(refreshInterval)
}
callback(file.length())
deferred.await()
}
gildor
05/29/2019, 7:46 AMMark
05/29/2019, 7:47 AMsuspend fun <R> (suspend (File) -> R).invokeAndTrackFileLength(file: File, refreshInterval: Long = 200L, callback: (Long) -> Unit): R = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
val notifyCallbackFun = suspend {
val length = file.length()
withContext(Dispatchers.Main) {
callback(length)
}
}
val deferred = async {
invoke(file)
}
while (deferred.isActive) {
notifyCallbackFun()
delay(refreshInterval)
}
notifyCallbackFun()
deferred.await()
}
gildor
05/29/2019, 8:32 AMelizarov
05/29/2019, 8:40 AMdeferred.await()
at the end is redundant. The await
is implicit at the end of the scope.gildor
05/29/2019, 8:42 AMdeferred.await()
returns result Relizarov
05/29/2019, 8:42 AMval notifyCallbackFun = suspend {
-->
suspend fun notifyCallbackFun() {
(just use a local function!)while (true) {
notifyCallbackFun()
if (!deferred.isActive) break
delay(refreshInterval)
}
And now you can just inline notifyCallbackFun
into this code.Mark
05/29/2019, 8:44 AMsuspend fun <R> (suspend (File) -> R).invokeAndTrackFileLength(file: File, refreshInterval: Long = 200L, callback: (Long) -> Unit): R {
return suspend { invoke(file) }.invokeAndTrackFileLength(file, refreshInterval, callback)
}
suspend fun <R> (suspend () -> R).invokeAndTrackFileLength(file: File, refreshInterval: Long = 200L, callback: (Long) -> Unit): R = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
if (refreshInterval <= 0L) {
throw IllegalArgumentException("invalid refreshInterval: $refreshInterval")
}
val deferred = async {
invoke()
}
var oldLength = -1L
while (deferred.isActive) {
file.length().takeIf { it != oldLength }?.let { len ->
withContext(Dispatchers.Main) {
if (deferred.isActive) {
callback(len)
}
}
//logd("notifying length: $it")
oldLength = len
}
delay(refreshInterval)
}
deferred.await().also {
val len = file.length()
withContext(Dispatchers.Main) {
callback(len)
}
}
}
block.invoke()
finishes. How to do this?gildor
05/29/2019, 9:03 AMinvokeOnComplete
registered on deferredMark
05/29/2019, 9:04 AMgildor
05/29/2019, 9:05 AMMark
05/29/2019, 9:07 AMsuspend fun <R> (suspend () -> R).invokeAndTrackFileLength(file: File, refreshInterval: Long = 200L, callback: (Long) -> Unit): R = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
val deferred = async {
invoke()
}
val trackJob = launch {
var oldLength = -1L
while (deferred.isActive) {
file.length().takeIf { it != oldLength }?.let { len ->
withContext(Dispatchers.Main) {
if (deferred.isActive) {
callback(len)
}
}
oldLength = len
}
delay(refreshInterval)
}
}
deferred.invokeOnCompletion {
trackJob.cancel()
}
deferred.await().also {
val len = file.length()
withContext(Dispatchers.Main) {
callback(len)
}
}
}
deferred.isActive()
calls are no longer necessary?gildor
05/29/2019, 9:11 AMMark
05/29/2019, 9:18 AMasync
? Could we just do: try {
invoke()
} finally {
trackJob.cancel()
val len = file.length()
withContext(Dispatchers.Main) {
callback(len)
}
}
gildor
05/29/2019, 9:22 AMMark
05/29/2019, 9:22 AMsuspend fun <R> (suspend () -> R).invokeAndTrackFileLength(file: File, refreshInterval: Long = 200L, callback: (Long) -> Unit): R = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
val trackJob = launch {
var oldLength = -1L
while (true) {
val len = file.length()
if (len != oldLength) {
withContext(Dispatchers.Main) {
callback(len)
}
oldLength = len
}
delay(refreshInterval)
}
}
try {
invoke()
} finally {
trackJob.cancel()
val len = file.length()
withContext(Dispatchers.Main) {
callback(len)
}
}
}
gildor
05/29/2019, 9:31 AMMark
05/29/2019, 9:55 AMwithContext(Dispatchers.Main)
. However, I worry that this means there will be too much context switching since the file.length() call is made much more often than the callback(len) call. suspend fun <R> (suspend () -> R).invokeAndTrackFileLength(file: File, refreshInterval: Long = 200L, callback: (Long) -> Unit): R = withContext(Dispatchers.Main) {
val trackJob = launch {
var oldLength = -1L
while (true) {
file.lengthIO().takeIf { it != oldLength }?.let { len ->
callback(len)
oldLength = len
}
delay(refreshInterval)
}
}
try {
invoke()
} finally {
trackJob.cancel()
callback(file.lengthIO())
}
}
suspend fun File.lengthIO() = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
length()
}
launch{...body...}
in a withContext(<http://Dispatchers.IO|Dispatchers.IO>)
to reduce the amount of context switching or maybe it doesn’t matter?gildor
05/29/2019, 9:58 AMMark
05/29/2019, 10:01 AMgildor
05/29/2019, 10:01 AMMark
05/29/2019, 10:02 AMgildor
05/29/2019, 10:02 AMMark
05/29/2019, 10:03 AMlen != oldLength
check is often falsegildor
05/29/2019, 10:04 AMMark
05/29/2019, 10:06 AMgildor
05/29/2019, 10:07 AMMark
05/29/2019, 10:07 AMcoroutineScope
) and so now use the appropriate dispatcher inline. Also added a local function 😉 suspend fun <R> (suspend () -> R).invokeAndTrackFileLength(file: File, refreshInterval: Long = 200L, callback: (Long) -> Unit): R = coroutineScope {
var oldLength = -1L
suspend fun notifyCallback(length: Long) {
if (length != oldLength) {
withContext(Dispatchers.Main) {
callback(length)
}
oldLength = length
}
}
val trackJob = launch(<http://Dispatchers.IO|Dispatchers.IO>) {
while (true) {
notifyCallback(file.length())
delay(refreshInterval)
}
}
try {
invoke()
} finally {
trackJob.cancel()
notifyCallback(file.lengthIO())
}
}
suspend fun File.lengthIO() = withContext(<http://Dispatchers.IO|Dispatchers.IO>) {
length()
}
Zach Klippenstein (he/him) [MOD]
05/29/2019, 5:09 PMgildor
05/29/2019, 11:22 PM