1

I know that in single coroutine scope all the operations inside it are executed sequentially. But why it doesn't work this way and how to make it work. Expected result is I clicked the button many times and it handles all my clicks one by one, so 10th Hello World must be in log in 10 secs. But actually all my clicks work asynchronously and 10th Hello World in log in ~2 sec

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val coroutineScope = CoroutineScope(Dispatchers.IO) btn.setOnClickListener { coroutineScope.launch { Log.d("myTag",hello()) } } } suspend fun hello(): String { delay(1000) return "Hello, World!" } } 
2
  • delay() is a suspend function, so Kotlin can switch control to another coroutine. So, your 10 clicks queue up 10 coroutines, and once the 1000ms delay elapses, each coroutine will be ready to run and will get a chance to do so. Commented Mar 13, 2020 at 23:12
  • ok, I supposed it but how to make it work? is it possible? Commented Mar 13, 2020 at 23:16

2 Answers 2

2

You could use a Channel and play with its buffer capacity, like this:

class MainActivity : AppCompatActivity() { val channel = Channel<Unit>(10) //Supports up to 10 clicks unattended override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val coroutineScope = CoroutineScope(Dispatchers.IO) btn.setOnClickListener { coroutineScope.launch { channel.send(Unit) } } coroutineScope.launch { for(i in channel) { Log.d("myTag",hello()) } } } suspend fun hello(): String { delay(1000) return "Hello, World!" } } 

Or if you prefer, you could consume clicks as a Flow, like this:

class MainActivity : AppCompatActivity() { val channel = Channel<Unit>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val coroutineScope = CoroutineScope(Dispatchers.IO) btn.setOnClickListener { coroutineScope.launch { channel.send(Unit) } } val clicksFlow = channel.consumeAsFlow() coroutineScope.launch { clicksFlow .buffer(Channel.BUFFERED) //Supports up to 64 clicks unattended .collect { Log.d("myTag",hello()) } } } suspend fun hello(): String { delay(1000) return "Hello, World!" } } 

Just make sure to close channel and cancel coroutineScope appropriately.


UPDATE

You could use a callbackFlow (like @MarkoTopolnik suggested), like this:

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val coroutineScope = CoroutineScope(Dispatchers.IO) val clicksFlow = callbackFlow<Unit> { btn.setOnClickListener { offer(Unit) } awaitClose { cancel() } } coroutineScope.launch { clicksFlow .buffer(Channel.BUFFERED) //Supports up to 64 clicks unattended .collect { Log.d("myTag",hello()) } } } suspend fun hello(): String { delay(1000) return "Hello, World!" } } 

Now you only have to make sure to cancel coroutineScope appropriately.

Sign up to request clarification or add additional context in comments.

Comments

0

Channels is a great way to achieve this, but you can also create own CoroutineDispatcher, like

val coroutineScope = CoroutineScope(Executors.newSingleThreadExecutor().asCoroutineDispatcher()) btn.setOnClickListener { coroutineScope.launch { channel.send(Unit) } } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.