Background Threads : lost FBGraphUser

I'm quite sure this is near the same thing that I've described here:
https://bugzilla.xamarin.com/show_bug.cgi?id=16208

But I want to make sure I'm not loosing something...

What I do is trying to process a large amount of FBGraphUsers in a background thread instantiated using NSThread.InvokeInBackground(). What is the purpose? We know Facebook API calls should be made from UI thread, so that's okay for me, the delegates with prepared FBGraphObjects are also called in UI thread - and that's okay too. But then I need to process those FBGraphObjects somehow and I don't want to block the UI anymore, so the decision is to make some background worker thread and push FBGraphObjects to worker's queue as they received (I make a lot of simultaneous requests!).

Here's a little example of what I do:

FBRequestConnection.StartWithGraphPath("/<userid>/?fields=...", (conn, result, error) => {
    var fbo = new FBGraphObject(result.Handle);
    EnqueueBackgroundWorkerAction( () => {
        ParseSomehow( fbo );
    });
});

EnqueueBackgroundWorkerAction() takes an action and puts it into worker's queue, after the worker have finished it's current task (more likely another ParseSomehow(), but maybe something else), it will invoke this action.

This means actually I don't put the FBGraphObjects into some collection or like this, the object's reference is stored 'somewhere there' behind the mechanisms that allows delegates to use variables from outside it's scope...

And all works pretty nice in 70-90% of the time, but other 10-30% are the thing making me crazy right now because I still can't find a workaround. In those 'rare' cases the c-sharp FBGraphObject objects are okay, they are not lost, reference is not null, but what should be inside is totally lost. To understand what I mean, please see the attached screenshot.

As we see there, NSObject was not disposed (?), but it's native handle is 0. I double-checked that I always receive an FBGraphObject with non-zero native handle, but then, while traversing into ParseSomehow() method, it is lost somewhere...

So the question is: am I doing something wrong? Should I care about some Auto Release Pools inside my background thread? Should I handle multithreading with NSObjects in a special manner? I even tried to Retain() my poor FBGraphObjects, it won't help...

Sorry for a long message, but I'm trying to describe the exact problem at once.

ps. Forgot to say, the similar things happens when I decompose FBGraphObjects and take some child FBGraphObjects using ObjectForKey() method. So I get some child object (for example, school) and then trying to extract the things from it, and again from time to time I'm getting nothing while it actually was there (I see it in my debug logs, every received with StartWithGraphPath() FBGraphObject is printed to console). So it happens not only between the threads, but inside the background thread itself! As I remember, that wasn't happening when all worked in UI thread... But that's not the thing I would like to do.

Posts

  • YuriTikhomirovYuriTikhomirov RUMember ✭✭

    Okay, I've made workaround next day after this post, but actually had no time to share.

    What I did is wrapped startWithGraphPath:completionHandler: call with my own helper class.

    The completion handler there won't automatically create c-sharp-managed NSObject over IntPtr, but will pass this IntPtr to my thread. The one extra thing I had to do is retaining and then releasing the object - otherwise obj-c garbage collector had no chance to know I'm still referring this object, I guess. (The rule is simple: if I don't retain, it crashes=)

    And this way all 100% of my objects are saved and successfully read in another thread. The bonus for me is that I've learned to work with obj-c blocks under Xamarin, maybe it is not as simple as working with IntPtr, but it is useful if you want to be a little more "native". By the way, I avoid to use unsafe code, all is done by marshaling.

    After all I want to share my code.

    public class FacebookRequest
    {
        private static readonly IntPtr sel_StartWithGraphPath = Selector.GetHandle ("startWithGraphPath:completionHandler:");
        private static readonly IntPtr classhandle_FBRequestConnection = Class.GetHandle ("FBRequestConnection");
    
        public delegate void FBNativeHandler(IntPtr connection, IntPtr result, IntPtr error);
        private delegate void DFBRequestHandler (IntPtr ptr, IntPtr connection, IntPtr result, IntPtr error);
    
        private FBNativeHandler handler;
        private FBRequestConnection fbConnection = null;
    
        public FacebookRequest(string request, FBNativeHandler handler)
        {
            if (handler == null)
                throw new ArgumentNullException ("handler");
            if (request == null)
                throw new ArgumentNullException ("request");
    
            this.handler = handler;
    
            MakeRequest(request);
        }
    
        [MonoTouch.MonoPInvokeCallback(typeof(DFBRequestHandler))]
        private static void TFBRequestHandler (IntPtr ptr, IntPtr connection, IntPtr result, IntPtr error)
        {
            BlockLiteral block = (BlockLiteral) Marshal.PtrToStructure<BlockLiteral>(ptr);
    
            FBNativeHandler fBRequestHandler = 
                (FBNativeHandler)((!(block.global_handle != IntPtr.Zero)) ? 
                    GCHandle.FromIntPtr (block.local_handle).Target : GCHandle.FromIntPtr (block.global_handle).Target);
            if (fBRequestHandler != null)
            {
                fBRequestHandler (connection, result, error);
            }
        }
    
        void MakeRequest(string request)
        {
            IntPtr intPtr = NSString.CreateNative (request);
    
            BlockLiteral blockLiteral = default(BlockLiteral);
            blockLiteral.SetupBlock (new DFBRequestHandler(TFBRequestHandler), new FBNativeHandler(Handler));
    
            IntPtr ptrBlock = Marshal.AllocHGlobal(Marshal.SizeOf<BlockLiteral>());
            Marshal.StructureToPtr(blockLiteral, ptrBlock, false);
    
            try 
            {
                fbConnection = (FBRequestConnection)Runtime.GetNSObject(
                    Messaging.IntPtr_objc_msgSend_IntPtr_IntPtr (
                        classhandle_FBRequestConnection, 
                        sel_StartWithGraphPath, 
                        intPtr, 
                        ptrBlock
                    )
                );
            }
            finally 
            {
                Marshal.FreeHGlobal(ptrBlock);
    
                blockLiteral.CleanupBlock ();
    
                NSString.ReleaseNative (intPtr);
            }
        }
    
        void Handler(IntPtr connection, IntPtr result, IntPtr error)
        {
            handler(connection, result, error);
        }
    }
    

    Helper class to do retain / release.

    public static class NSUtils
    {
        private static readonly IntPtr sel_ObjRetain = Selector.GetHandle ("retain");
        private static readonly IntPtr sel_ObjRelease = Selector.GetHandle ("release");
    
        public static void NSObjectRetain(this IntPtr ptr)
        {
            Messaging.IntPtr_objc_msgSend(ptr, sel_ObjRetain);
        }
    
        public static void NSObjectRelease(this IntPtr ptr)
        {
            Messaging.IntPtr_objc_msgSend(ptr, sel_ObjRelease);
        }
    }
    

    And finally the way it works from the handling standpoint:

        void FriendReceivedNative(IntPtr pConnection, IntPtr pResult, IntPtr pError)
        {
            var error = pError != IntPtr.Zero ? new NSError(pError) : null;
            if (error == null && pResult != IntPtr.Zero)
            {
                pResult.NSObjectRetain();
                myThreadPool.AddSomeTask(
                    () => { 
                        FBGraphUser user = new FBGraphUser(pResult);
                        TakeFriend(user);
                        user.Release();
                        // should we maybe Dispose() right now?
                    }
                );
            }
            else
            {
                TakeError(error.ToString());
            }
        }
    
Sign In or Register to comment.