Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin suspend fun

I have Kotlin interface

interface FileSystem {
    suspend fun getName(path: Path): List<String>
}

How I can call it from Java? What is

Continuation <? super List<String>>

enter image description here

like image 576
LeshaRB Avatar asked Aug 12 '18 12:08

LeshaRB


People also ask

What is suspend fun in Kotlin?

The definition of suspend function: Suspend function is a function that could be started, paused, and resume.

Is suspend the same as async?

It is not the same as async.

What is the difference between suspending vs blocking?

Hunting to know BLOCKING vs SUSPENDINGA process is blocked when there is some external reason that it can not be restarted, e.g., an I/O device is unavailable, or a semaphore file is locked. A process is suspended means that the OS has stopped executing it, but that could just be for time-slicing (multitasking).

Should I use runBlocking?

runBlocking() - use when you need to bridge regular and suspending code in synchronous way, by blocking the thread. Just don't overuse it. Don't treat runBlocking() as a quick fix for calling suspend functions. Always think if instead you shouldn't propagate suspending to the caller.


1 Answers

Kotlin implements coroutines using a mix of the regular stack-based calling convention and the continuation-passing style (CPS). To achieve that, it performs a CPS transformation on all suspend funs by adding an implicit parameter, an object you can use to continue the program from the place where the function was called. That's how Kotlin manages to pull off the trick to suspend the execution within your function's body: it extracts the continuation object, saves it somewhere, and then makes your function return (without yet having produced its value). Later on it can achieve the effect of jumping into the middle of your function body by invoking the continuation object.

The continuation is basically a callback object, just like those familiar from async Java APIs. Instead of returning its result, the suspendable function passes its result to the continuation. To call a suspend fun from java, you'll have to create such a callback. Here's an example:

Continuation<List<String>> myCont = new Continuation<List<String>>() {
    @Override public void resume(List<String> result) {
        System.out.println("Result of getName is " + result);
    }
    @Override public void resumeWithException(Throwable throwable) {
        throwable.printStackTrace();
    }
    @NotNull @Override public CoroutineContext getContext() {
        return Unconfined.INSTANCE;
    }
};

NOTE: The above works only with experimental coroutines. In the final API there's just one resumption method: resumeWith(result: Result<T>) where Result is a discriminated union of the result type and the internal class Failure, which makes it inaccessible from Java.

Let's also create a mock implementation of the FileSystem interface:

class MockFs : FileSystem {
    override suspend fun getName(path: Path): List<String> {
        suspendCoroutine<Unit> {
            println("getName suspended")
        }
        println("getName resumed")
        return listOf("usr", "opt")
    }
}

Now we're ready to call it from Java:

Object result = new MockFs().getName(Paths.get(""), myCont);
System.out.println("getName returned " + result);

It prints

getName suspended
getName returned
kotlin.coroutines.experimental.intrinsics.CoroutineSuspendedMarker@6ce253f1

getName() returned a special marker object that signals the function got suspended. The function will pass its actual result to our callback, once it resumes.

Let us now improve MockFs so we can get access to the continuation:

class MockFs : FileSystem {
    var continuation : Continuation<Unit>? = null

    override suspend fun getName(path: Path): List<String> {
        suspendCoroutine<Unit> {
            continuation = it
            println("getName suspended")
        }
        println("getName resumed")
        return listOf("usr", "opt")
    }
}

Now we'll be able to manually resume the continuation. We can use this code:

MockFs mockFs = new MockFs();
mockFs.getName(Paths.get(""), myCont);
mockFs.getContinuation().resume(Unit.INSTANCE);

This will print

getName suspended
getName resumed
Result of getName is [usr, opt]

In real life a suspendable function will use some mechanism to get itself resumed when the result becomes available. For example, if it's a wrapper around some async API call, it will register a callback. When the async API invokes the callback, it will in turn invoke our continuation. You shouldn't need to manually resume it like we did in our mock code.

A suspend fun also has the option to just return its result directly. For example, with this MockFs code

class MockFs : FileSystem {
    override suspend fun getName(path: Path) = listOf("usr", "opt") 
}

in Java we can just say

System.out.println(new MockFs().getName(Paths.get(""), myCont));

and it will print [usr, opt]. We could even have passed in an empty implementation of Continuation.

The most demanding case happens when you don't know in advance whether the function will suspend itself or not. In such a case a good approach is to write the following at the call site:

Object retVal = mockFs.getName(Paths.get(""), myCont);
if (retVal != IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
    myCont.resume((List<String>) retVal);
}

Otherwise you'll have to duplicate the code that handles the function's result.

like image 158
Marko Topolnik Avatar answered Jan 01 '23 09:01

Marko Topolnik