[mono-android] Getting intermittent calls to AsyncTask(IntPtr, Android.Runtime.JniHandleOwnership)

Jonathan Pryor jonp at xamarin.com
Thu Feb 16 16:29:46 UTC 2012


On Feb 16, 2012, at 12:01 AM, chris at Terrago wrote:
> I occasionally get external calls to my class' constructor with the following prototype: 	
> AsyncTask(IntPtr, Android.Runtime.JniHandleOwnership).

The (IntPtr, JniHandleOwnership) constructor is needed whenever a Java object will be exposed to managed code and a wrapper needs to be created. For example, if some Java code creates a java.lang.String instance and that String is exposed to managed code (via method invocation), then the Java.Lang.String(IntPtr, JniHandleOwnership) constructor will be called to wrap the java.lang.String instance.

Once a wrapper has been created, an internal mapping is kept between the Java object and the managed wrapper. This mapping is maintained until both the Java and managed objects are collected by both the Dalvik and Mono GCs, OR until Java.Lang.Object.Dispose() is invoked. Calling Dispose() will break the mapping, allowing both objects to be collected earlier than they otherwise would be.

Here's the problem: if you have an AsyncTask subclass that you create, the creation will implicitly create a corresponding Java object and the mapping between the Java instance and your C# instance. If there is a request for an AsyncTask(IntPtr, JniHandleOwnership) constructor, that means that the mapping was somehow broken, and the Java instance is being re-exposed to managed code.

The problem here is that the execution of a constructor implies the creation of a new instance, meaning the "loss" of any instance state (because you now have a new object with `null`ed out instance data). This is very likely Bad™, as it will likely result in very subtle bugs ("why is this variable null?!"). :-)

I would check your code to ensure that you're not calling Dispose() on your AsyncTask instance, or if you are calling Dispose() that you're not calling it prematurely. You should only call Dispose() when you KNOW that either:

1. the instance is "just" a wrapper, e.g. Java.Lang.String instances are "just" wrappers over Java instances. For such wrappers, it doesn't matter if the Java object re-enters managed code and a new wrapper is created.

2. the corresponding Java instance will NEVER be exposed to managed code again, and thus a wrapper will not need to be created for it.

> If I include a constructor of this form in my class as a pass through, everything appears fine
...
> I have noticed that this call immediately precedes the throwing of an exception in code called by my AsyncTask

Is that exception a NullReferenceException? :-)

 - Jon



More information about the Monodroid mailing list