diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 57dc3c02dc..cf9e857e25 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -56,10 +56,12 @@ dependencies {
     def support_version = '28.0.0'
     def moshi_version = '1.8.0'
     def lifecycle_version = "1.1.1"
+    def coroutines_version = "1.0.1"
 
     implementation fileTree(dir: 'libs', include: ['*.aar'])
     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
-    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0'
+    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
+    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
 
     implementation "com.android.support:appcompat-v7:$support_version"
     implementation "com.android.support:recyclerview-v7:$support_version"
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/Task.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/Task.kt
new file mode 100644
index 0000000000..84c457d755
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/Task.kt
@@ -0,0 +1,9 @@
+package im.vector.matrix.android.internal
+
+import arrow.core.Try
+
+interface Task<in PARAMS, out RESULT> {
+
+    fun execute(params: PARAMS): Try<RESULT>
+
+}
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/TaskExecutor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/TaskExecutor.kt
new file mode 100644
index 0000000000..e720a24c67
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/TaskExecutor.kt
@@ -0,0 +1,27 @@
+package im.vector.matrix.android.internal
+
+import im.vector.matrix.android.api.MatrixCallback
+import im.vector.matrix.android.api.util.Cancelable
+import im.vector.matrix.android.internal.util.CancelableCoroutine
+import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import timber.log.Timber
+
+internal class TaskExecutor(private val coroutineDispatchers: MatrixCoroutineDispatchers) {
+
+    fun <PARAMS, RESULT> executeTask(task: Task<PARAMS, RESULT>,
+                                     params: PARAMS,
+                                     callback: MatrixCallback<RESULT>): Cancelable {
+        val job = GlobalScope.launch(coroutineDispatchers.main) {
+            val resultOrFailure = withContext(coroutineDispatchers.io) {
+                Timber.v("Executing ${task.javaClass} on ${Thread.currentThread().name}")
+                task.execute(params)
+            }
+            resultOrFailure.fold({ callback.onFailure(it) }, { callback.onSuccess(it) })
+        }
+        return CancelableCoroutine(job)
+    }
+
+}
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt
index 154777d0e4..b0c0634d3c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/di/MatrixModule.kt
@@ -1,11 +1,10 @@
 package im.vector.matrix.android.internal.di
 
 import im.vector.matrix.android.api.MatrixOptions
-import im.vector.matrix.android.api.thread.MainThreadExecutor
+import im.vector.matrix.android.internal.TaskExecutor
 import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
 import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.asCoroutineDispatcher
 import org.koin.dsl.module.module
 
 
@@ -18,7 +17,11 @@ class MatrixModule(private val options: MatrixOptions) {
         }
 
         single {
-            MatrixCoroutineDispatchers(io = Dispatchers.IO, computation = Dispatchers.IO, main = MainThreadExecutor().asCoroutineDispatcher())
+            MatrixCoroutineDispatchers(io = Dispatchers.IO, computation = Dispatchers.IO, main = Dispatchers.Main)
+        }
+
+        single {
+            TaskExecutor(get())
         }
 
         single {
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt
similarity index 74%
rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataRequest.kt
rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt
index 47aa3308de..d1954956f4 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataRequest.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/DefaultGetGroupDataTask.kt
@@ -5,38 +5,30 @@ import arrow.core.fix
 import arrow.instances.`try`.monad.monad
 import arrow.typeclasses.binding
 import com.zhuinden.monarchy.Monarchy
-import im.vector.matrix.android.api.MatrixCallback
-import im.vector.matrix.android.api.util.Cancelable
+import im.vector.matrix.android.internal.Task
 import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
 import im.vector.matrix.android.internal.database.query.where
 import im.vector.matrix.android.internal.network.executeRequest
 import im.vector.matrix.android.internal.session.group.model.GroupRooms
 import im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse
 import im.vector.matrix.android.internal.session.group.model.GroupUsers
-import im.vector.matrix.android.internal.util.CancelableCoroutine
-import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
 import im.vector.matrix.android.internal.util.tryTransactionSync
 import io.realm.kotlin.createObject
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
 
-internal class GetGroupDataRequest(
+internal interface GetGroupDataTask : Task<GetGroupDataTask.Params, Unit> {
+
+    data class Params(val groupId: String)
+
+}
+
+
+internal class DefaultGetGroupDataTask(
         private val groupAPI: GroupAPI,
-        private val monarchy: Monarchy,
-        private val coroutineDispatchers: MatrixCoroutineDispatchers
-) {
+        private val monarchy: Monarchy
+) : GetGroupDataTask {
 
-    fun execute(groupId: String,
-                callback: MatrixCallback<Boolean>
-    ): Cancelable {
-        val job = GlobalScope.launch(coroutineDispatchers.main) {
-            val groupOrFailure = getGroupData(groupId)
-            groupOrFailure.fold({ callback.onFailure(it) }, { callback.onSuccess(true) })
-        }
-        return CancelableCoroutine(job)
-    }
-
-    fun getGroupData(groupId: String): Try<Unit> {
+    override fun execute(params: GetGroupDataTask.Params): Try<Unit> {
+        val groupId = params.groupId
         return Try.monad().binding {
 
             val groupSummary = executeRequest<GroupSummaryResponse> {
@@ -55,6 +47,7 @@ internal class GetGroupDataRequest(
         }.fix()
     }
 
+
     private fun insertInDb(groupSummary: GroupSummaryResponse,
                            groupRooms: GroupRooms,
                            groupUsers: GroupUsers,
@@ -77,7 +70,6 @@ internal class GetGroupDataRequest(
                     groupSummaryEntity.userIds.clear()
                     groupSummaryEntity.userIds.addAll(userIds)
 
-
                 }
     }
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt
index ce4a8c10eb..828c883d35 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GetGroupDataWorker.kt
@@ -20,7 +20,7 @@ internal class GetGroupDataWorker(context: Context,
             val deletionIndexes: List<Int>
     )
 
-    private val getGroupDataRequest by inject<GetGroupDataRequest>()
+    private val getGroupDataTask by inject<GetGroupDataTask>()
 
     override fun doWork(): Result {
         val params = WorkerParamsFactory.fromData<Params>(inputData)
@@ -35,7 +35,7 @@ internal class GetGroupDataWorker(context: Context,
     }
 
     private fun fetchGroupData(groupId: String): Try<Unit> {
-        return getGroupDataRequest.getGroupData(groupId)
+        return getGroupDataTask.execute(GetGroupDataTask.Params(groupId))
     }
 
 }
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupModule.kt
index d1dd75ffdb..30da4c81d7 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupModule.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/group/GroupModule.kt
@@ -14,7 +14,7 @@ class GroupModule {
         }
 
         scope(DefaultSession.SCOPE) {
-            GetGroupDataRequest(get(), get(), get())
+            DefaultGetGroupDataTask(get(), get()) as GetGroupDataTask
         }
 
     }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt
index 2fd869b3b4..c24eb4be8f 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/DefaultRoom.kt
@@ -14,12 +14,13 @@ import im.vector.matrix.android.api.session.room.model.Membership
 import im.vector.matrix.android.api.session.room.model.MyMembership
 import im.vector.matrix.android.api.session.room.model.RoomSummary
 import im.vector.matrix.android.api.util.Cancelable
+import im.vector.matrix.android.internal.TaskExecutor
 import im.vector.matrix.android.internal.database.mapper.asDomain
 import im.vector.matrix.android.internal.database.model.RoomEntity
 import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
 import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
 import im.vector.matrix.android.internal.database.query.where
-import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersRequest
+import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
 import im.vector.matrix.android.internal.session.sync.SyncTokenStore
 import org.koin.core.parameter.parametersOf
 import org.koin.standalone.KoinComponent
@@ -30,11 +31,12 @@ internal data class DefaultRoom(
         override val myMembership: MyMembership
 ) : Room, KoinComponent {
 
-    private val loadRoomMembersRequest by inject<LoadRoomMembersRequest>()
+    private val loadRoomMembersTask by inject<LoadRoomMembersTask>()
     private val syncTokenStore by inject<SyncTokenStore>()
     private val monarchy by inject<Monarchy>()
     private val timelineHolder by inject<TimelineHolder> { parametersOf(roomId) }
     private val sendService by inject<SendService> { parametersOf(roomId) }
+    private val taskExecutor by inject<TaskExecutor>()
 
     override val roomSummary: LiveData<RoomSummary> by lazy {
         val liveData = monarchy
@@ -56,7 +58,8 @@ internal data class DefaultRoom(
             object : Cancelable {}
         } else {
             val token = syncTokenStore.getLastToken()
-            loadRoomMembersRequest.execute(roomId, token, Membership.LEAVE, object : MatrixCallback<Boolean> {})
+            val params = LoadRoomMembersTask.Params(roomId, token, Membership.LEAVE)
+            taskExecutor.executeTask(loadRoomMembersTask, params, object : MatrixCallback<Boolean> {})
         }
     }
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt
index a823124c54..0baf406b9b 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/RoomModule.kt
@@ -5,9 +5,16 @@ import im.vector.matrix.android.api.session.room.SendService
 import im.vector.matrix.android.api.session.room.TimelineHolder
 import im.vector.matrix.android.api.session.room.send.EventFactory
 import im.vector.matrix.android.internal.session.DefaultSession
-import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersRequest
+import im.vector.matrix.android.internal.session.room.members.DefaultLoadRoomMembersTask
+import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
 import im.vector.matrix.android.internal.session.room.send.DefaultSendService
-import im.vector.matrix.android.internal.session.room.timeline.*
+import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineHolder
+import im.vector.matrix.android.internal.session.room.timeline.DefaultGetContextOfEventTask
+import im.vector.matrix.android.internal.session.room.timeline.DefaultPaginationTask
+import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
+import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
+import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback
+import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
 import im.vector.matrix.android.internal.util.PagingRequestHelper
 import org.koin.dsl.module.module
 import retrofit2.Retrofit
@@ -24,7 +31,7 @@ class RoomModule {
         }
 
         scope(DefaultSession.SCOPE) {
-            LoadRoomMembersRequest(get(), get(), get())
+            DefaultLoadRoomMembersTask(get(), get()) as LoadRoomMembersTask
         }
 
         scope(DefaultSession.SCOPE) {
@@ -32,11 +39,11 @@ class RoomModule {
         }
 
         scope(DefaultSession.SCOPE) {
-            PaginationRequest(get(), get(), get())
+            DefaultPaginationTask(get(), get()) as PaginationTask
         }
 
         scope(DefaultSession.SCOPE) {
-            GetContextOfEventRequest(get(), get(), get())
+            DefaultGetContextOfEventTask(get(), get()) as GetContextOfEventTask
         }
 
         scope(DefaultSession.SCOPE) {
@@ -46,8 +53,8 @@ class RoomModule {
 
         factory { (roomId: String) ->
             val helper = PagingRequestHelper(Executors.newSingleThreadExecutor())
-            val timelineBoundaryCallback = TimelineBoundaryCallback(roomId, get(), get(), helper)
-            DefaultTimelineHolder(roomId, get(), timelineBoundaryCallback, get()) as TimelineHolder
+            val timelineBoundaryCallback = TimelineBoundaryCallback(roomId, get(), get(), get(), helper)
+            DefaultTimelineHolder(roomId, get(), get() , timelineBoundaryCallback, get()) as TimelineHolder
         }
 
         factory { (roomId: String) ->
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersRequest.kt
deleted file mode 100644
index c3fca61d7b..0000000000
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersRequest.kt
+++ /dev/null
@@ -1,64 +0,0 @@
-package im.vector.matrix.android.internal.session.room.members
-
-import arrow.core.Try
-import com.zhuinden.monarchy.Monarchy
-import im.vector.matrix.android.api.MatrixCallback
-import im.vector.matrix.android.api.session.room.model.Membership
-import im.vector.matrix.android.api.util.Cancelable
-import im.vector.matrix.android.internal.database.helper.addStateEvents
-import im.vector.matrix.android.internal.database.model.RoomEntity
-import im.vector.matrix.android.internal.database.query.where
-import im.vector.matrix.android.internal.network.executeRequest
-import im.vector.matrix.android.internal.session.room.RoomAPI
-import im.vector.matrix.android.internal.util.CancelableCoroutine
-import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
-import im.vector.matrix.android.internal.util.tryTransactionSync
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-internal class LoadRoomMembersRequest(private val roomAPI: RoomAPI,
-                                      private val monarchy: Monarchy,
-                                      private val coroutineDispatchers: MatrixCoroutineDispatchers) {
-
-    fun execute(roomId: String,
-                streamToken: String?,
-                excludeMembership: Membership? = null,
-                callback: MatrixCallback<Boolean>
-    ): Cancelable {
-        val job = GlobalScope.launch(coroutineDispatchers.main) {
-            val responseOrFailure = execute(roomId, streamToken, excludeMembership)
-            responseOrFailure.fold({ callback.onFailure(it) }, { callback.onSuccess(true) })
-        }
-        return CancelableCoroutine(job)
-    }
-
-    //TODO : manage stream token (we have 404 on some rooms actually)
-    private suspend fun execute(roomId: String,
-                                streamToken: String?,
-                                excludeMembership: Membership?) = withContext(coroutineDispatchers.io) {
-
-        executeRequest<RoomMembersResponse> {
-            apiCall = roomAPI.getMembers(roomId, null, null, excludeMembership?.value)
-        }.flatMap { response ->
-            insertInDb(response, roomId)
-        }
-    }
-
-    private fun insertInDb(response: RoomMembersResponse, roomId: String): Try<RoomMembersResponse> {
-        return monarchy
-                .tryTransactionSync { realm ->
-                    // We ignore all the already known members
-                    val roomEntity = RoomEntity.where(realm, roomId).findFirst()
-                                     ?: throw IllegalStateException("You shouldn't use this method without a room")
-
-                    val roomMembers = RoomMembers(realm, roomId).getLoaded()
-                    val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) }
-
-                    roomEntity.addStateEvents(eventsToInsert)
-                    roomEntity.areAllMembersLoaded = true
-                }
-                .map { response }
-    }
-
-}
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersTask.kt
new file mode 100644
index 0000000000..04ebaa197e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/members/LoadRoomMembersTask.kt
@@ -0,0 +1,51 @@
+package im.vector.matrix.android.internal.session.room.members
+
+import arrow.core.Try
+import com.zhuinden.monarchy.Monarchy
+import im.vector.matrix.android.api.session.room.model.Membership
+import im.vector.matrix.android.internal.Task
+import im.vector.matrix.android.internal.database.helper.addStateEvents
+import im.vector.matrix.android.internal.database.model.RoomEntity
+import im.vector.matrix.android.internal.database.query.where
+import im.vector.matrix.android.internal.network.executeRequest
+import im.vector.matrix.android.internal.session.room.RoomAPI
+import im.vector.matrix.android.internal.util.tryTransactionSync
+
+internal interface LoadRoomMembersTask : Task<LoadRoomMembersTask.Params, Boolean> {
+
+    data class Params(
+            val roomId: String,
+            val streamToken: String?,
+            val excludeMembership: Membership? = null
+    )
+}
+
+internal class DefaultLoadRoomMembersTask(private val roomAPI: RoomAPI,
+                                          private val monarchy: Monarchy
+) : LoadRoomMembersTask {
+
+    override fun execute(params: LoadRoomMembersTask.Params): Try<Boolean> {
+        return executeRequest<RoomMembersResponse> {
+            apiCall = roomAPI.getMembers(params.roomId, null, null, params.excludeMembership?.value)
+        }.flatMap { response ->
+            insertInDb(response, params.roomId)
+        }.map { true }
+    }
+
+    private fun insertInDb(response: RoomMembersResponse, roomId: String): Try<RoomMembersResponse> {
+        return monarchy
+                .tryTransactionSync { realm ->
+                    // We ignore all the already known members
+                    val roomEntity = RoomEntity.where(realm, roomId).findFirst()
+                                     ?: throw IllegalStateException("You shouldn't use this method without a room")
+
+                    val roomMembers = RoomMembers(realm, roomId).getLoaded()
+                    val eventsToInsert = response.roomMemberEvents.filter { !roomMembers.containsKey(it.stateKey) }
+
+                    roomEntity.addStateEvents(eventsToInsert)
+                    roomEntity.areAllMembersLoaded = true
+                }
+                .map { response }
+    }
+
+}
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultGetContextOfEventTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultGetContextOfEventTask.kt
new file mode 100644
index 0000000000..c4fc483196
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultGetContextOfEventTask.kt
@@ -0,0 +1,31 @@
+package im.vector.matrix.android.internal.session.room.timeline
+
+import arrow.core.Try
+import im.vector.matrix.android.internal.Task
+import im.vector.matrix.android.internal.network.executeRequest
+import im.vector.matrix.android.internal.session.room.RoomAPI
+import im.vector.matrix.android.internal.util.FilterUtil
+
+internal interface GetContextOfEventTask : Task<GetContextOfEventTask.Params, TokenChunkEvent> {
+
+    data class Params(
+            val roomId: String,
+            val eventId: String
+    )
+
+}
+
+internal class DefaultGetContextOfEventTask(private val roomAPI: RoomAPI,
+                                            private val tokenChunkEventPersistor: TokenChunkEventPersistor
+) : GetContextOfEventTask {
+
+    override fun execute(params: GetContextOfEventTask.Params): Try<EventContextResponse> {
+        val filter = FilterUtil.createRoomEventFilter(true)?.toJSONString()
+        return executeRequest<EventContextResponse> {
+            apiCall = roomAPI.getContextOfEvent(params.roomId, params.eventId, 0, filter)
+        }.flatMap { response ->
+            tokenChunkEventPersistor.insertInDb(response, params.roomId, PaginationDirection.BACKWARDS).map { response }
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt
new file mode 100644
index 0000000000..30f7a32e26
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultPaginationTask.kt
@@ -0,0 +1,39 @@
+package im.vector.matrix.android.internal.session.room.timeline
+
+import arrow.core.Try
+import arrow.core.failure
+import im.vector.matrix.android.internal.Task
+import im.vector.matrix.android.internal.network.executeRequest
+import im.vector.matrix.android.internal.session.room.RoomAPI
+import im.vector.matrix.android.internal.util.FilterUtil
+
+
+internal interface PaginationTask : Task<PaginationTask.Params, TokenChunkEvent> {
+
+    data class Params(
+            val roomId: String,
+            val from: String?,
+            val direction: PaginationDirection,
+            val limit: Int
+    )
+
+}
+
+internal class DefaultPaginationTask(private val roomAPI: RoomAPI,
+                                     private val tokenChunkEventPersistor: TokenChunkEventPersistor
+) : PaginationTask {
+
+    override fun execute(params: PaginationTask.Params): Try<TokenChunkEvent> {
+        if (params.from == null) {
+            return RuntimeException("From token shouldn't be null").failure()
+        }
+        val filter = FilterUtil.createRoomEventFilter(true)?.toJSONString()
+        return executeRequest<PaginationResponse> {
+            apiCall = roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter)
+        }.flatMap { chunk ->
+            tokenChunkEventPersistor
+                    .insertInDb(chunk, params.roomId, params.direction)
+                    .map { chunk }
+        }
+    }
+}
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt
index ba7acc2a02..a78297c2f3 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimelineHolder.kt
@@ -8,6 +8,7 @@ import im.vector.matrix.android.api.MatrixCallback
 import im.vector.matrix.android.api.session.events.interceptor.EnrichedEventInterceptor
 import im.vector.matrix.android.api.session.events.model.EnrichedEvent
 import im.vector.matrix.android.api.session.room.TimelineHolder
+import im.vector.matrix.android.internal.TaskExecutor
 import im.vector.matrix.android.internal.database.mapper.asDomain
 import im.vector.matrix.android.internal.database.model.ChunkEntityFields
 import im.vector.matrix.android.internal.database.model.EventEntity
@@ -22,14 +23,14 @@ private const val PAGE_SIZE = 30
 
 internal class DefaultTimelineHolder(private val roomId: String,
                                      private val monarchy: Monarchy,
+                                     private val taskExecutor: TaskExecutor,
                                      private val boundaryCallback: TimelineBoundaryCallback,
-                                     private val contextOfEventRequest: GetContextOfEventRequest
+                                     private val contextOfEventTask: GetContextOfEventTask
 ) : TimelineHolder {
 
     private val eventInterceptors = ArrayList<EnrichedEventInterceptor>()
 
     init {
-        boundaryCallback.limit = 30
         eventInterceptors.add(MessageEventInterceptor(monarchy, roomId))
     }
 
@@ -76,7 +77,8 @@ internal class DefaultTimelineHolder(private val roomId: String,
 
     private fun fetchEventIfNeeded(eventId: String) {
         if (!isEventPersisted(eventId)) {
-            contextOfEventRequest.execute(roomId, eventId, object : MatrixCallback<EventContextResponse> {})
+            val params = GetContextOfEventTask.Params(roomId, eventId)
+            taskExecutor.executeTask(contextOfEventTask, params, object : MatrixCallback<TokenChunkEvent> {})
         }
     }
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetContextOfEventRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetContextOfEventRequest.kt
deleted file mode 100644
index f4f792d1bb..0000000000
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetContextOfEventRequest.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-package im.vector.matrix.android.internal.session.room.timeline
-
-import im.vector.matrix.android.api.MatrixCallback
-import im.vector.matrix.android.api.util.Cancelable
-import im.vector.matrix.android.internal.network.executeRequest
-import im.vector.matrix.android.internal.session.room.RoomAPI
-import im.vector.matrix.android.internal.util.CancelableCoroutine
-import im.vector.matrix.android.internal.util.FilterUtil
-import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-internal class GetContextOfEventRequest(private val roomAPI: RoomAPI,
-                                        private val tokenChunkEventPersistor: TokenChunkEventPersistor,
-                                        private val coroutineDispatchers: MatrixCoroutineDispatchers
-) {
-
-    fun execute(roomId: String,
-                eventId: String,
-                callback: MatrixCallback<EventContextResponse>
-    ): Cancelable {
-        val job = GlobalScope.launch(coroutineDispatchers.main) {
-            val filter = FilterUtil.createRoomEventFilter(true)?.toJSONString()
-            val contextOrFailure = execute(roomId, eventId, filter)
-            contextOrFailure.fold({ callback.onFailure(it) }, { callback.onSuccess(it) })
-        }
-        return CancelableCoroutine(job)
-    }
-
-    private suspend fun execute(roomId: String,
-                                eventId: String,
-                                filter: String?) = withContext(coroutineDispatchers.io) {
-
-        executeRequest<EventContextResponse> {
-            apiCall = roomAPI.getContextOfEvent(roomId, eventId, 0, filter)
-        }.flatMap { response ->
-            tokenChunkEventPersistor.insertInDb(response, roomId, PaginationDirection.BACKWARDS).map { response }
-        }
-    }
-
-
-}
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetEventRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetEventRequest.kt
deleted file mode 100644
index 28abd07225..0000000000
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetEventRequest.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package im.vector.matrix.android.internal.session.room.timeline
-
-import com.zhuinden.monarchy.Monarchy
-import im.vector.matrix.android.api.MatrixCallback
-import im.vector.matrix.android.api.session.events.model.Event
-import im.vector.matrix.android.api.util.Cancelable
-import im.vector.matrix.android.internal.network.executeRequest
-import im.vector.matrix.android.internal.session.room.RoomAPI
-import im.vector.matrix.android.internal.util.CancelableCoroutine
-import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-internal class GetEventRequest(private val roomAPI: RoomAPI,
-                               private val monarchy: Monarchy,
-                               private val coroutineDispatchers: MatrixCoroutineDispatchers
-) {
-
-    fun execute(roomId: String,
-                eventId: String,
-                callback: MatrixCallback<Event>
-    ): Cancelable {
-        val job = GlobalScope.launch(coroutineDispatchers.main) {
-            val eventOrFailure = execute(roomId, eventId)
-            eventOrFailure.fold({ callback.onFailure(it) }, { callback.onSuccess(it) })
-        }
-        return CancelableCoroutine(job)
-    }
-
-    private suspend fun execute(roomId: String,
-                                eventId: String) = withContext(coroutineDispatchers.io) {
-
-        executeRequest<Event> {
-            apiCall = roomAPI.getEvent(roomId, eventId)
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetEventTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetEventTask.kt
new file mode 100644
index 0000000000..596d5801de
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/GetEventTask.kt
@@ -0,0 +1,22 @@
+package im.vector.matrix.android.internal.session.room.timeline
+
+import arrow.core.Try
+import im.vector.matrix.android.api.session.events.model.Event
+import im.vector.matrix.android.internal.Task
+import im.vector.matrix.android.internal.network.executeRequest
+import im.vector.matrix.android.internal.session.room.RoomAPI
+
+internal class GetEventTask(private val roomAPI: RoomAPI
+) : Task<GetEventTask.Params, Event> {
+
+    internal data class Params(
+            val roomId: String,
+            val eventId: String
+    )
+
+    override fun execute(params: Params): Try<Event> {
+        return executeRequest {
+            apiCall = roomAPI.getEvent(params.roomId, params.eventId)
+        }
+    }
+}
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt
deleted file mode 100644
index e94d038717..0000000000
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/PaginationRequest.kt
+++ /dev/null
@@ -1,52 +0,0 @@
-package im.vector.matrix.android.internal.session.room.timeline
-
-import arrow.core.failure
-import im.vector.matrix.android.api.MatrixCallback
-import im.vector.matrix.android.api.util.Cancelable
-import im.vector.matrix.android.internal.network.executeRequest
-import im.vector.matrix.android.internal.session.room.RoomAPI
-import im.vector.matrix.android.internal.util.CancelableCoroutine
-import im.vector.matrix.android.internal.util.FilterUtil
-import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-internal class PaginationRequest(private val roomAPI: RoomAPI,
-                                 private val tokenChunkEventPersistor: TokenChunkEventPersistor,
-                                 private val coroutineDispatchers: MatrixCoroutineDispatchers
-) {
-
-    fun execute(roomId: String,
-                from: String?,
-                direction: PaginationDirection,
-                limit: Int,
-                callback: MatrixCallback<TokenChunkEvent>
-    ): Cancelable {
-        val job = GlobalScope.launch(coroutineDispatchers.main) {
-            val filter = FilterUtil.createRoomEventFilter(true)?.toJSONString()
-            val chunkOrFailure = execute(roomId, from, direction, limit, filter)
-            chunkOrFailure.fold({ callback.onFailure(it) }, { callback.onSuccess(it) })
-        }
-        return CancelableCoroutine(job)
-    }
-
-    private suspend fun execute(roomId: String,
-                                from: String?,
-                                direction: PaginationDirection,
-                                limit: Int,
-                                filter: String?) = withContext(coroutineDispatchers.io) {
-
-        if (from == null) {
-            return@withContext RuntimeException("From token shouldn't be null").failure<TokenChunkEvent>()
-        }
-        executeRequest<PaginationResponse> {
-            apiCall = roomAPI.getRoomMessagesFrom(roomId, from, direction.value, limit, filter)
-        }.flatMap { chunk ->
-            tokenChunkEventPersistor
-                    .insertInDb(chunk, roomId, direction)
-                    .map { chunk }
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt
index bab802e24d..623237a71c 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/TimelineBoundaryCallback.kt
@@ -4,18 +4,20 @@ import android.arch.paging.PagedList
 import com.zhuinden.monarchy.Monarchy
 import im.vector.matrix.android.api.MatrixCallback
 import im.vector.matrix.android.api.session.events.model.EnrichedEvent
+import im.vector.matrix.android.internal.TaskExecutor
 import im.vector.matrix.android.internal.database.model.ChunkEntity
 import im.vector.matrix.android.internal.database.query.findAllIncludingEvents
 import im.vector.matrix.android.internal.util.PagingRequestHelper
 import java.util.*
 
 internal class TimelineBoundaryCallback(private val roomId: String,
-                                        private val paginationRequest: PaginationRequest,
+                                        private val taskExecutor: TaskExecutor,
+                                        private val paginationTask: PaginationTask,
                                         private val monarchy: Monarchy,
                                         private val helper: PagingRequestHelper
 ) : PagedList.BoundaryCallback<EnrichedEvent>() {
 
-    var limit = 10
+    var limit = 30
 
     override fun onZeroItemsLoaded() {
         // actually, it's not possible
@@ -44,13 +46,12 @@ internal class TimelineBoundaryCallback(private val roomId: String,
             val chunkEntity = ChunkEntity.findAllIncludingEvents(realm, Collections.singletonList(item.root.eventId)).firstOrNull()
             token = if (direction == PaginationDirection.FORWARDS) chunkEntity?.nextToken else chunkEntity?.prevToken
         }
-        paginationRequest.execute(
-                roomId = roomId,
-                from = token,
-                direction = direction,
-                limit = limit,
-                callback = createCallback(requestCallback)
-        )
+        val params = PaginationTask.Params(roomId = roomId,
+                                           from = token,
+                                           direction = direction,
+                                           limit = limit)
+
+        taskExecutor.executeTask(paginationTask, params, createCallback(requestCallback))
     }
 
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt
index dca339c018..17634f6d15 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncModule.kt
@@ -36,7 +36,7 @@ internal class SyncModule {
         }
 
         scope(DefaultSession.SCOPE) {
-            SyncRequest(get(), get(), get())
+            DefaultSyncTask(get(), get()) as SyncTask
         }
 
         scope(DefaultSession.SCOPE) {
@@ -44,7 +44,7 @@ internal class SyncModule {
         }
 
         scope(DefaultSession.SCOPE) {
-            SyncThread(get(), get(), get(), get())
+            SyncThread(get(), get(), get(), get(), get())
         }
 
     }
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncRequest.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncRequest.kt
deleted file mode 100644
index d3348dea70..0000000000
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncRequest.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package im.vector.matrix.android.internal.session.sync
-
-import im.vector.matrix.android.api.MatrixCallback
-import im.vector.matrix.android.api.util.Cancelable
-import im.vector.matrix.android.internal.network.executeRequest
-import im.vector.matrix.android.internal.session.filter.FilterBody
-import im.vector.matrix.android.internal.session.sync.model.SyncResponse
-import im.vector.matrix.android.internal.util.CancelableCoroutine
-import im.vector.matrix.android.internal.util.FilterUtil
-import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-internal class SyncRequest(private val syncAPI: SyncAPI,
-                           private val coroutineDispatchers: MatrixCoroutineDispatchers,
-                           private val syncResponseHandler: SyncResponseHandler) {
-
-
-    fun execute(token: String?, callback: MatrixCallback<SyncResponse>): Cancelable {
-        val job = GlobalScope.launch {
-            val syncOrFailure = execute(token)
-            syncOrFailure.fold({ callback.onFailure(it) }, { callback.onSuccess(it) })
-        }
-        return CancelableCoroutine(job)
-    }
-
-    private suspend fun execute(token: String?) = withContext(coroutineDispatchers.io) {
-        val params = HashMap<String, String>()
-        val filterBody = FilterBody()
-        FilterUtil.enableLazyLoading(filterBody, true)
-        var timeout = 0
-        if (token != null) {
-            params["since"] = token
-            timeout = 30000
-        }
-        params["timeout"] = timeout.toString()
-        params["filter"] = filterBody.toJSONString()
-        executeRequest<SyncResponse> {
-            apiCall = syncAPI.sync(params)
-        }.flatMap { syncResponse ->
-            syncResponseHandler.handleResponse(syncResponse, token, false)
-        }
-    }
-
-}
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt
new file mode 100644
index 0000000000..533019d31e
--- /dev/null
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/SyncTask.kt
@@ -0,0 +1,41 @@
+package im.vector.matrix.android.internal.session.sync
+
+import arrow.core.Try
+import im.vector.matrix.android.internal.Task
+import im.vector.matrix.android.internal.network.executeRequest
+import im.vector.matrix.android.internal.session.filter.FilterBody
+import im.vector.matrix.android.internal.session.sync.model.SyncResponse
+import im.vector.matrix.android.internal.util.FilterUtil
+
+internal interface SyncTask : Task<SyncTask.Params, SyncResponse> {
+
+    data class Params(val token: String?)
+
+}
+
+internal class DefaultSyncTask(private val syncAPI: SyncAPI,
+                               private val syncResponseHandler: SyncResponseHandler
+) : SyncTask {
+
+
+    override fun execute(params: SyncTask.Params): Try<SyncResponse> {
+        val requestParams = HashMap<String, String>()
+        val filterBody = FilterBody()
+        FilterUtil.enableLazyLoading(filterBody, true)
+        var timeout = 0
+        if (params.token != null) {
+            requestParams["since"] = params.token
+            timeout = 30000
+        }
+        requestParams["timeout"] = timeout.toString()
+        requestParams["filter"] = filterBody.toJSONString()
+
+        return executeRequest<SyncResponse> {
+            apiCall = syncAPI.sync(requestParams)
+        }.flatMap { syncResponse ->
+            syncResponseHandler.handleResponse(syncResponse, params.token, false)
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt
index 67617bfba2..0a523a135b 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt
@@ -3,8 +3,9 @@ package im.vector.matrix.android.internal.session.sync.job
 import im.vector.matrix.android.api.MatrixCallback
 import im.vector.matrix.android.api.failure.Failure
 import im.vector.matrix.android.api.util.Cancelable
+import im.vector.matrix.android.internal.TaskExecutor
 import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
-import im.vector.matrix.android.internal.session.sync.SyncRequest
+import im.vector.matrix.android.internal.session.sync.SyncTask
 import im.vector.matrix.android.internal.session.sync.SyncTokenStore
 import im.vector.matrix.android.internal.session.sync.model.SyncResponse
 import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
@@ -13,10 +14,11 @@ import java.util.concurrent.CountDownLatch
 
 private const val RETRY_WAIT_TIME_MS = 10_000L
 
-internal class SyncThread(private val syncRequest: SyncRequest,
+internal class SyncThread(private val syncTask: SyncTask,
                           private val networkConnectivityChecker: NetworkConnectivityChecker,
                           private val syncTokenStore: SyncTokenStore,
-                          private val backgroundDetectionObserver: BackgroundDetectionObserver
+                          private val backgroundDetectionObserver: BackgroundDetectionObserver,
+                          private val taskExecutor: TaskExecutor
 ) : Thread(), NetworkConnectivityChecker.Listener, BackgroundDetectionObserver.Listener {
 
     enum class State {
@@ -30,7 +32,7 @@ internal class SyncThread(private val syncRequest: SyncRequest,
     private var state: State = State.IDLE
     private val lock = Object()
     private var nextBatch = syncTokenStore.getLastToken()
-    private var cancelableRequest: Cancelable? = null
+    private var cancelableTask: Cancelable? = null
 
     fun restart() {
         synchronized(lock) {
@@ -57,7 +59,7 @@ internal class SyncThread(private val syncRequest: SyncRequest,
         synchronized(lock) {
             Timber.v("Kill sync...")
             state = State.KILLING
-            cancelableRequest?.cancel()
+            cancelableTask?.cancel()
             lock.notify()
         }
     }
@@ -77,7 +79,8 @@ internal class SyncThread(private val syncRequest: SyncRequest,
             } else {
                 Timber.v("Execute sync request...")
                 val latch = CountDownLatch(1)
-                cancelableRequest = syncRequest.execute(nextBatch, object : MatrixCallback<SyncResponse> {
+                val params = SyncTask.Params(nextBatch)
+                cancelableTask = taskExecutor.executeTask(syncTask, params, object : MatrixCallback<SyncResponse> {
                     override fun onSuccess(data: SyncResponse) {
                         nextBatch = data.nextBatch
                         syncTokenStore.saveToken(nextBatch)