Issue
I have run into this problem.
I have (at least) 6 coroutines which works on a map which is managed through a mutex.
Sometimes I need to cancel one, more or all the coroutines in different scenarios.
What is the best way to cope with the mutex when cancelling the coroutine(s) ? (The fact is that I really don't know if the cancelling coroutine was the one which locked the mutex). Do the mutex "system" has any neat trick to cope with this ?
ADDITION 2021.09.30 11:28 GMT+2 (DST)
My coding is fairly complex, so I simplify it and show the main problem here
...
class HomeFragment:Fragment(){
...
private lateinit var googleMap:GoogleMap
val mapMutex = Mutex()
...
override fun onViewCreated(view:View, savedInstanceState: Bundle?) {
...
binding.fragmentHomeMapView?.geMapAsync { _googleMap ->
_googleMap?.let{ safeGoogleMap ->
googleMap = safeGoogleMap
}?:let{
Message.error("Error creating map (null)")
}
...
homeViewModel.apply {
...
//observer & coroutine 1
liveDataMapFlagged?.observe(
viewLifeCycleOwner
){flaggedMapDetailResult->
//Here I want to stop the lifecycleScope job below if it is already
//running and do some cleanup before entering (do I need to access the
//mutex if cleanup influence the google map ?)
//If I cancel the job, will the mutex then unlock gracefully ?
flaggedMapDetailResult?.apply {
...
lifecycleScope.launchWhenStarted { //Here I want to catch the job with i.e 'flagJob = lifeCycleScope.launchWhe...'
...
withContext(Dispatchers.Default){
...
mapMutex.withLock { //suspends if locked
withContext(Dispatchers.Main){
selectedSiteMarker?.remove()
selectedCircle?.remove()
... // Doing some cleanup... removing markers
}
... // Creating new markers
var flaggedSiteMarkerLatLng = coordinateSiteLatitude?.let safeLatitude@{safeLatitude->
return@safeLatitude coordinateSiteLongitude?.let safeLongitude@{safeLongitude->
return@safeLongitude LatLng(safeLatitude,safeLongitude)
}
}
...
flaggedSiteMarkerLatLng?.let { safeFlaggedSiteMarkerLatLng ->
val selectedSiteOptions =
MarkerOptions()
.position(safeFlaggedSiteMarkerLatLng)
.anchor(0.5f,0.5f)
.visible(flaggedMarkerState)
.flat(true)
.zIndex(10f)
.title(setTicketNumber(ticketNumber))
.snippet(appointmentName?:"Name is missing")
.icon(vSelectedSiteIcon)
selectedSiteMarker = withContext(Dispatchers.Main){
googleMap.addMarker(selectedSiteOptions)?.also{
it.tag = siteId
}
}
... //Do some more adding
} //End mutex
...
}//End dispatchers default
...
}//End lifecycleScope.launchWhenStarted
...
}?:let{//End apply
...//Cleanup if no data present
lifeCycleScope.launchWhenStarted{ //Shoud harvest Job and stop above
//if it is called before ending...
//if necessary
mapMutex.withLock{
//Cleanup markers
}
}
}
...
}//End observer 1
//observer 2
liveDataMapListFromFiltered2?.observer(
viewLifeCycleOwner
){mapDetailList ->
//Should check if job below is running and cancel gracefully and
//clean up data
...//Do some work on mapDetailList and create new datasets
lifecycleScope.launchWhenStarted{ //Scope start (should harvest job)
...
withContext(Dispatchers.Default) //Default context
{
...//Do some heavy work on list (no need for mutex)
}
mapMutex.withLock {
withContext(Dispatchers.Main)
{
//Do work on googlemap. Move camera etc.
}
}
...//Do other not map related work
mapMutex.withLock {
withContext(Dispatchers.Main)
{
//Do work on googlemap. Move camera etc.
}
}
...//Do other not map related work
mapMutex.withLock {
withContext(Dispatchers.Main)
{
//Do work on googlemap. Move camera etc.
}
}//end mutex
}//end scope
}//end observer 2
}//end viewmode
}//end gogleMap
Solution
Generally, a cancel
is a normal exception, which you can just catch in order to run clean-up operations, you can see the example on closing resources.
In addition, since you can still by cancelled during clean-up, for critical operations you can prevent further cancellation. Put together your job can have something like:
my_mutex.lock()
try {
// locked stuff
} finally {
withContext(NonCancellable) {
// clean up
my_mutex.unlock()
}
}
I think NonCancellable
is overdoing it in the case of only an unlock since it is supposed to be atomic, but I am not sure. If this is the case, I just looked up this pattern and apparently this is so common they have something more nifty:
mutex.withLock {
// locked stuff
}
As it says in the link
There is also withLock extension function that conveniently represents
mutex.lock(); try { ... } finally { mutex.unlock() }
pattern.
Answered By - kabanus
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.