Bug 60951 - Android.Database Cursor loader is not loading in background thread and blocking the UI when accessing device contacts, problem is when they are 100's of contacts in the device
Summary: Android.Database Cursor loader is not loading in background thread and blocki...
Status: REOPENED
Alias: None
Product: Android
Classification: Xamarin
Component: General ()
Version: 8.0 (15.4)
Hardware: PC Mac OS
: --- normal
Target Milestone: ---
Assignee: Jonathan Pryor
URL:
Depends on:
Blocks:
 
Reported: 2017-12-01 23:04 UTC by Kalyan Mungi
Modified: 2018-01-17 21:17 UTC (History)
3 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
code (2.14 KB, text/plain)
2017-12-01 23:04 UTC, Kalyan Mungi
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report for Bug 60951 on Developer Community or GitHub if you have new information to add and do not yet see a matching new report.

If the latest results still closely match this report, you can use the original description:

  • Export the original title and description: Developer Community HTML or GitHub Markdown
  • Copy the title and description into the new report. Adjust them to be up-to-date if needed.
  • Add your new information.

In special cases on GitHub you might also want the comments: GitHub Markdown with public comments

Related Links:
Status:
REOPENED

Description Kalyan Mungi 2017-12-01 23:04:12 UTC
Created attachment 25891 [details]
code

android accessing contacts using CursorLoader,, When I'm trying to read contacts in the device, UI is blocking for 15 sec when i have 1700 contacts on my device. I tried to run the cursor in the Task.Run,, which is throwing exception - "Cannot create handler inside a thread that has not created it".

My code is in the attachment
Comment 1 Jonathan Pryor 2017-12-04 19:31:54 UTC
It would be useful if the *exact* error message and stack trace that you observed were provided.

My *guess* is that you're seeing a `RuntimeException`, a'la:

https://stackoverflow.com/questions/3875184/cant-create-handler-inside-thread-that-has-not-called-looper-prepare
https://stackoverflow.com/questions/5009816/cant-create-handler-inside-thread-which-has-not-called-looper-prepare

Assuming that's the case, the problem is your use of `Task.Run()` with `CursorLoader`, as `CursorLoader` -- via `AsyncTaskLoader` -- uses `android.os.Handler` (which uses `Looper`) in order to load items. It *does not* use a dedicated thread.

Meanwhile, `Task.Run()` *does* use a new/separate thread. The result is that there is no `Looper` for the Task's thread, which causes `CursorLoader` to throw that `RuntimeException`.

Or so I assume.

If that's the case, I would suggest *providing* a looper instance for the thread. Change this:

```csharp
address = await Task.Run (
    async () => await RetrieveAddressAsync (loader, id)
);
```

to this:

```csharp
address = await Task.Run (sync () => {
  using (var looper = Android.OS.Looper.MyLooper ()) {
    return await RetrieveAddressAsync (loader, id);
  }
});
```

Note: not tested!
Comment 2 Kalyan Mungi 2017-12-04 20:15:15 UTC
I used your looper code and it throwed the same exception.

Here is the stack trace

  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
  at Java.Interop.JniEnvironment+InstanceMethods.CallNonvirtualVoidMethod (Java.Interop.JniObjectReference instance, Java.Interop.JniObjectReference type, Java.Interop.JniMethodInfo method, Java.Interop.JniArgumentValue* args) [0x00089] in <7cfbebb561c54efc9010b018c0846c7e>:0 
  at Android.Runtime.JNIEnv.CallNonvirtualVoidMethod (System.IntPtr jobject, System.IntPtr jclass, System.IntPtr jmethod, Android.Runtime.JValue* parms) [0x00015] in <2cbc8bc37f564147a10de1abb0c4e399>:0 
  at Android.Runtime.JNIEnv.FinishCreateInstance (System.IntPtr instance, System.IntPtr jclass, System.IntPtr constructorId, Android.Runtime.JValue* constructorParameters) [0x00008] in <2cbc8bc37f564147a10de1abb0c4e399>:0 
  at Android.Support.V4.Content.CursorLoader..ctor (Android.Content.Context context, Android.Net.Uri uri, System.String[] projection, System.String selection, System.String[] selectionArgs, System.String sortOrder) [0x00160] in <27c17fe440cf491ba8255bcefade6e02>:0 
  at MobileX.Droid.Services.ContactsService+<RetrieveAddressAsync>d__1.MoveNext () [0x00010] in MobileX.Droid/Services/ContactsService.cs:89 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x0003e] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
  at System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () [0x00000] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
  at MobileX.Droid.Services.ContactsService+<>c__DisplayClass0_1+<<GetContacts>b__0>d.MoveNext () [0x00059] in MobileX/Droid/Providers/Services/ContactsService.cs:51 
--- End of stack trace from previous location where exception was thrown ---
  at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) [0x0003e] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) [0x00028] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
  at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) [0x00008] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
  at System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () [0x00000] in <896ad1d315ca4ba7b117efb8dacaedcf>:0 
  at MobileX.Droid.Services.ContactsService+<GetContacts>d__0.MoveNext () [0x00155] in MobileX.Droid/Services/ContactsService.cs:49 
  --- End of managed Java.Lang.RuntimeException stack trace ---
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
	at android.os.Handler.<init>(Handler.java:200)
	at android.os.Handler.<init>(Handler.java:114)
	at android.support.v4.content.Loader$ForceLoadContentObserver.<init>(Loader.java:54)
	at android.support.v4.content.CursorLoader.<init>(CursorLoader.java:132)
Comment 3 Kalyan Mungi 2017-12-04 20:17:48 UTC
To overcome this error,, i just have to run the code in UI thread but that is blocking the UI for 15 secs, when i'm trying to load 1700 of my device contacts into the app.
Comment 4 Jonathan Pryor 2017-12-05 20:50:38 UTC
@Kalyan: The exception says:

> Can't create handler inside thread that has not called Looper.prepare()

I would suggest trying that. Additionally, as per the Looper documentation:
https://developer.android.com/reference/android/os/Looper.html

which states that `Looper.loop()` also needs to be invoked.

What I don't know is if `Looper.loop()` is a blocking call.

You could thus try:


```csharp
address = await Task.Run (sync () => {
  Android.OS.Looper.Prepare ();
  using (var looper = Android.OS.Looper.MyLooper ()) {
    try {
      return await RetrieveAddressAsync (loader, id);
    } finally {
      Android.OS.Looper.Quit ();
    }
  }
});
```

Which may or may not work.

Question: what calls GetContacts()? Is it from the UI thread, or from a non-UI thread? 

Question: Why are you using `await Task.Run()` in the first place?

Instead of:

> address = await Task.Run (async () => await RetrieveAddressAsync (loader, id));

Why not do:

> address = await RetrieveAddressAsync (loader, id);

That should ensure that execution runs on the UI thread -- assuming GetContacts() is called from the UI thread -- which would allow `loader` to use the UI thread Looper.
Comment 5 Kalyan Mungi 2017-12-05 21:21:47 UTC
Your looper code still throws the same exception


GetContacts() is called from non-UI thread.

I'm trying to free the UI thread from being blocked,, so using task.run to run that method on background thread.

My problem is UI blocking,,,If i dont use the task.run,,,there wont be any exceptions and my code works as expected but UI thread is blocking..

Like you said,,, the following code is running on main thread and blocking the UI

> address = await RetrieveAddressAsync(loader,id);

That's the reason i want to load that method on the background thread or any other way that UI should be responsive..
Comment 6 Kalyan Mungi 2017-12-05 21:51:51 UTC
Fixed the issue,,,, thanks
Comment 7 Kalyan Mungi 2017-12-13 19:02:48 UTC
@Jonathan :

Is there a way to create multiple cursor loaders to access the contacts fast enough.

When i try to load 1700 contacts,,, it is taking 1.5 minute to load the contacts completely.

Main thread blocking issue is no more but the contacts access is still slow..
Comment 8 nmilcoff 2018-01-17 19:56:54 UTC
I was also having this issue. The code I'm using to fetch contacts is similar to @Kalyan Mungi's code.

This solution proposed by @Jonathan Pryor:

```csharp
address = await Task.Run (sync () => {
  Android.OS.Looper.Prepare ();
  using (var looper = Android.OS.Looper.MyLooper ()) {
    try {
      return await RetrieveAddressAsync (loader, id);
    } finally {
      looper.Quit ();
    }
  }
});
```

Worked for me. (Please note I changed the line `Android.OS.Looper.Quit ();` to be `looper.Quit();`).
Comment 9 Kalyan Mungi 2018-01-17 20:31:43 UTC
@nmilcoff :

Is your contacts fetch faster? When i have some hundreds of contacts in the device, it is taking too long to fetch them.
Comment 10 nmilcoff 2018-01-17 21:17:00 UTC
Well I guess contacts fetching has never been super fast on Android, but as it doesn't block the UI anymore it's not a real problem. 

So no, I don't think it's working any faster.