I have this function:
suspend fun functionCall(): Job {
return MainScope().launch {
var i = 0
while(i < 3) {
i++
delay(3000)
yield()
}
}
cancel()
}
And I am calling from an external function when a button is clicked:
MainScope().launch {
if(functionCall().isActive) {
functionCall().cancelAndJoin()
}
}
Both of these functions are being run in a repository class.
And it is still iterating through the whole while loop even after the above if statement is triggered. What I'm noticing while debugging is "i" is also being reset to 0 which could indicate the job is being triggered more than once but it is definitely being triggered only once so I'm confused about what is happening.
What I want to happen is after that if statement for the entire job to cancel and for the entire function to return and run no more code.
I've also tried while(ensureActive) and the same thing is happening.
How do I do this?
Since this is Android, you should launch your UI-related coroutines from lifecycleScope. If you have a job that needs to survive screen rotations, you should launch it from inside a ViewModel from viewModelScope, and it must not touch any UI elements.
If you want to cancel a specific coroutine when an event happens, you should store that coroutine Job in a property so you can call cancel() on it. So a typical pattern inside an Activity for example might be:
private var fooJob: Job? = null
private fun fooSomething() {
fooJob = lifecycleScope.launch {
repeat(5) {
delay(1000)
Log.i("count", it.toString())
}
}
}
private fun cancelCurrentFoo() {
fooJob?.cancel()
}
Suppose you have a coroutine job you can start by calling one of the functions of your ViewModel, but you want the Activity/Fragment to be able to cancel it early. Then you expose a function that returns the coroutine Job:
fun foo() = viewModelScope.launch {
repeat(5) {
delay(1000)
Log.i("count", it.toString())
}
}
The Activity can call this function and it gets a Job instance in return that it can call cancel() on whenever it wants.
Related
I'm trying to replicate a Worker Pool in Kotlin: https://gobyexample.com/worker-pools
It works wonderful, but my problem is that I get OutOfMemoryError because all object references from the worker coroutines are kept in the heap as long as the coroutine is running. How can I avoid this problem?
Here is my code:
I create a channel in Service A and receive the data everytime a channel object is received.
class ServiceA(
) {
val channel = Channel<Pair<String,ByteArray>>(10000)
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
#PostConstruct
fun createWorkerGroup(){
coroutineScope.launch{
for(x in 1..5){
launch {
println("Create Worker $x")
while (true){
uploadImage(channel.receive() )
}
}
}
}
}
private suspend fun uploadImage(urlAndImage: Pair<String, ByteArray>){
val (url,image) = urlAndImage
println("Uploading Image: $url")
}
In my controller method I send my data to a channel:
uploadService.channel.send(Pair(url, image.bytes))
The worker pool can be automatically handled by the coroutine scope with an appropriate dispatcher.
If the image upload does blocking operations, you might want to use the IO dispatcher, like so: CoroutineScope(Dispatchers.IO.limitedParallelism(5)). I have omitted the SupervisorJob because you do not need it for the parent coroutine that will be created in createWorkerGroup(), but for the ones created by it. Don't forget to create the logic for cancelling the CoroutineScope when it is no longer needed.
After that, you can launch coroutines at will with no performance overhead, in the same place you did before:
#PostConstruct
fun createWorkerGroup() {
coroutineScope.launch{
supervisorScope {
channel.consumeEach {
launch {
uploadImage(it)
}
}
}
}
}
This is the correct approach for creating and using the worker pool, but you will need to test it in order to see if it eliminates the OutOfMemoryError. You might also want to try reducing the channel's capacity. Good luck!
Thank you #Halex for the help, here is my Kotlin coroutine worker pool with proper garbage collection
private val coroutineScope = CoroutineScope(Dispatchers.Default)
#OptIn(ExperimentalCoroutinesApi::class)
private val superVisorScope = CoroutineScope(SupervisorJob() + Dispatchers.IO.limitedParallelism(5))
#PostConstruct
fun createWorkerGroup() {
coroutineScope.launch {
superVisorScope.launch {
channel.consumeEach {
launch {
uploadImage(it)
}
}
}
}
coroutineScope.cancel()
}
Trying to get lastLocation and once it's done call api. But somehow once location is obtained my api calls always running in mainThread, so i'm getting exception:
android.io.NetworkOnMainThreadException
Here is my location observer:
fun getLocation(): Single<Location> {
return Single.create<Location> { subscriber ->
fusedLocationClient.lastLocation.addOnSuccessListener {
if (it != null) {
subscriber.onSuccess(it)
} else {
subscriber.onError(Exception("No location"))
}
}
}
}
Code that does some transformations
val locationObserver = getLocation()
observables.add(locationObserver.flatMap { _ -> sendDataToServer(data)})
Observer
Single.zip(observables) { args1 -> args1 }.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe({
Timber.i("Success")
}, {
Timber.i("Error %s", observables.size, it.localizedMessage)
it.printStackTrace()
})
I've set subscribeOn so it shouldn't be on mainThread, but looks like something missed.
Found that if i will use something like Single.just("One").flatMap{ ... } that will work fine and will be executed on non-main thread.
Is there something to do with getLocation function?
The order of subscribeOn, observeOn, subscribe, and transformations matters. Apparently, it's needed to do the transformations, in this case, the flatMap after specifying the observer thread with observeOn to make sure the code is executed in the right thread.
I am stuck with a very annoying issue. I want to create an Observable that emits some values after the initialization of some component. The problem is that the other component behaves strange and calls back on the main thread.
strangeComponent.init(Callback)
its callback can be an error or success:
interface Callback {
fun onError()
fun onSuccess() <-- this is always called on the main thread
}
Now I want to create an Observable that will only run once the init call was done:
val initSubject = CompletableSubject.create()
<T> fun withInit(call : (ObservableEmitter<T>) -> Unit) =
initSubject
.doOnSubscribe { startInit() }
.andThen(Observable.create{ emitter ->
call(emitter)
})
fun startInit() {
if (!initSubject.hasComplete()) {
strangeComponent.init(object : Callback {
override fun onSuccess() {
// will be called from the main thread
initSubject.onComplete()
}
...
}
}
}
And I use this like:
withInit {
//---- heavy code ------
it.onNext("Hallo")
it.onNext("World")
//---------
}.subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
log("received $it")
}
So it turns out that the heavy code part is always called from the main
thread instead of the computation thread. How do I access the given subscribeOn schedular and how do I wrap a callback so that its thread model does not affect the observable chain?
I tried all kind of constructs including serialized and AsyncSubject etc to
fix this but it is always consistent.
I'm trying to call some code within the vaadin framework which is longer running that will update the screen using push, however if the process is taking too long I want to be able to cancel it.
With that in mind I'm trying to use Guava's SimpleTimeLimiter class but no matter what I do I can't seem to stop the Vaadin process from stopping. I've tried both to put the SimpleTimeLimiter inside UI.getCurrent().access() method and outside of it but they both just continue to execute the process even if SimpleTimeLimiter throws a TimeoutException. However if I use the same code with a normal thread it seems to work...
public static void limitExecutionTime(Consumer<UI> lambda)
{
UI currentUI = UI.getCurrent();
UI.getCurrent().access(() ->
{
try
{
SimpleTimeLimiter.create(Executors.newSingleThreadExecutor()).callWithTimeout(new Callable<Void>()
{
#Override
public Void call()
{
// This is needed to deal how Vaadin 8 handles UI's
UI.setCurrent(currentUI);
lambda.accept();
return null;
}
}, 1, TimeUnit.SECONDS);
} catch (TimeoutException | InterruptedException | ExecutionException e) {
NotificationUtils.showError("Execution took beyond the maximum allowed time.");
currentUI.push();
}
});
}
In the above code if the method takes more than 1 second it will throw a TimeoutException and put up the notification window. However it will continue to execute the lambda.
As a result I've tried to do the opposite and put UI.getCurrent().access() in the public Void call() method but this had the exact same result...
You should call UI.access after your background task is ready to update it with some data. You use access method to do changes on the page that the user is viewing.
Background task execution
In your example, you are missing a way to pass task cancellation message to call method. In order to prepare for task cancellation from external event (for example cancel button click) then you need to take this into account in inside the task. The following example shows how you can offer cancel method using Future.cancel.
private void onCancelClick(Button.ClickEvent clickEvent) {
// This method is called from Vaadin UI thread. We will signal
// background task thread to stop.
futureResult.cancel(true);
}
Inside the actual task this can be handled in the following ways
private void simulateLongAndSlowCalculation() {
while (moreWorkTodo) {
if (Thread.currentThread().isInterrupted()) {
return;
}
try {
doSomeBlockingCallThatCanBeInterrupted();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
}
}
Starting task and UI.access
When starting task then view should create task and submit it to executor service.
private void onButtonClick(Button.ClickEvent clickEvent) {
// This runTask creates important link to current UI and the background task.
// "this" object in these onTask methods is the UI object that we want
// to update. We need to have someway to pass UI object to background
// thread. UI.getCurrent() could be a parameter that is passed to the
// task as well.
Future<String> futureResult = taskService.runTask(
this::onTaskDone,
this::onTaskCancel,
this::onTaskProgress);
progressDialog = new ProgressDialog(futureResult);
progressDialog.show();
}
Now UI.access method is only needed when we want to update UI. In this example, that can happen in the following cases
Task completed successfully
Task progress was updated
Task got cancelled
Note that all of the following methods this refers to the UI object that started the task. So we are updating the correct UI with result and not some other user's UI.
You should not need to call UI.setCurrent in your code.
private void onTaskProgress(double progress) {
logger.info("onTaskProgress: {}", progress);
access(() -> progressDialog.setProgress(progress));
}
private void onTaskCancel() {
logger.info("onTaskCancel");
access(() -> {
progressDialog.close();
setResult("Cancelled");
});
}
private void onTaskDone(String result) {
logger.info("onTaskDone");
access(() -> {
progressDialog.close();
setResult(result);
});
}
Example project
I pushed another project to github that shows how to cancel a background task from cancel button:
https://github.com/m1kah/vaadin-background-task
Edit: Added sections about background tasks and UI.access. Updated example project link to another example.
I am having some trouble with executing some logic when a subscription has been unsubscribed. I've been at this for hours and I have made little progress so far. This is a simplified version of my code:
public class Command<E> {
public CommandActionObservable execute() {
final CommandAction<E> command = createCommand();
final OnSubscribe<CommandAction<E>> onSubscribe = (subscriber) -> {
/* Create a listener that handles notifications and register it.
* The idea here is to push the command downstream so it can be re-executed
*/
final Listener listener = (event) -> {
subscriber.onNext(command);
}
registerListener(listener);
/* This is where I'm having trouble. The unregister method
* should be executed when the subscriber unsubscribed,
* but it never happens
*/
subscriber.add(Subscriptions.create(() -> {
unregisterListener(listener);
}));
// pass the initial command downstream
subscriber.onNext(command);
kickOffBackgroundAction();
}
final Observable<CommandAction<E>> actionObservable = Observable.create(onSubscribe)
.onBackpressureLatest()
.observeOn(Shedulers.io())
.onBackpressureLatest();
return new CommandActionObservable((subscriber) -> {
actionObservable.unsafeSubscribe(subscriber);
})
}
public class CommandActionObservable extends Observable<CommandAction<E> {
// default constructor omitted
public Observable<E> toResult() {
return lift((Operator) (subscriber) -> {
return new Subscriber<CommandAction<E>>() {
// delegate onCompleted and onError to subscriber
public void onNext(CommandAction<E> action) {
// execute the action and pass the result downstream
final E result = action.execute();
subscriber.onNext(result)
}
}
}
}
}
}
I am using the Command in the usual way, adding the resulting subscription to a CompositeSubscription and unsubscribing from it in onDestroy(). Here is an example:
final Observable<SomeType> obs = new Command<SomeType>()
.execute()
.toResult();
subscription.add(obs.subscribe(// impl here));
public void onDestroy() {
super.onDestroy();
subscription.unsubscribe();
}
As mentioned, I can't get the unsubscription logic to work and unregister the listener, which causes memory leaks in the app. If I call doOnUnsubscribe() on obs it gets called, so I am unsubscibing correctly, but maybe the nesting of the observables and lifting causes some issues.
I'd be glad to head opinions on this one.
Turns out it was way easier than I anticipated.
After a bit of digging around I was able to come up with the answer on my own. Just posting this for people that may end up in the same situation as me.
So, as I mentioned in my question, if I added a doOnSubscribe() action to the observable I was getting in my Activity, it gets notified. Next I tried adding the same action on the inner Observables I'm creating in the execute() method. They were not getting called. So, I came to the conclusion that the chain was getting broken somewhere between the observable in my activity and the observables I was creating in execute().
The only thing that was happening to the stream was the application of my custom Operator implemented in toResult(). After a Google search, I came across this excellent article - Pitfalls of Operator Implementation. I was indeed braking the chain in my operator and the upstream observables were not notified of the unsubscription.
After I did what the author advices, all is good. Here is what I needed to do:
lift((Operator) (subscriber) -> {
// connect the upstream and downstream subscribers to keep the chain intact
new Subscriber<CommandAction<E>>(subscriber) {
// the implementation is the same
}
}