Hot questions for Using EventBus in multithreading

Top 10 Java Open Source / EventBus / multithreading

Question:

I am using guava event bus. I have a server-like object that is supposed to be running all the time, listening for events to be posted to the b us. So in a junit test (MyObject is class under test), I create it in its own thread to simulate this and prevent blocking:

  @Test    
  public void test() {
      EventBus eventBus = new EventBus();

      Thread thread= new Thread() {
         @Override
         public void run()
         {
            logger.debug("Creating new thread");
            MyObject myObject = new MyObject(eventBus);
         }
      };

      thread.start();
      ...
  }

All is good, myObject is created off in its own thread, Thread1. Then later, I post an event to the event bus in my test:

eventBus.post(triggerObject);

The weird thing I am finding is that all the actions/logging from my subscribed method inside of MyObject class are executing in the main thread again. myObject waits for responses from certain other parts, and this blocks my test since it's in the main thread. Why is this happening? Is it something I am doing wrong with the EventBus or Java threading?


Answer:

Well you don't do nothing in the created thread except creating an object which finally ends up in heap (which is shared between threads), but since the reference to it is to not maintained after run, then it's also lost.

Your @Subscribe method from myObject is invoked in the same thread that calls eventBus.post(event); and not in the thread that created myObject.

The weird thing I am finding is that all the actions/logging from my subscribed method inside of MyObject class are executing in the main thread again

If your MyObject class has an @Subscribe method, then why does it need an instance of EventBus inside the constructor? You probably want

MyObject myObject = new MyObject();
eventBus.register(myObject);`

instead of MyObject myObject = new MyObject(eventBus);

Question:

i have a singleton service class that pulls data from a server on a set schedule. as soon as the client has received the data, i trigger bus.post(new NewServerResponseEvent()); (http://square.github.io/otto/)

then in my fragments i do this:

@Override
public void onResume() {
    super.onResume();
    eventBus.register(this);
}

@Override
public void onPause() {
    super.onPause();
    eventBus.unregister(this);
}

@Subscribe
public void handleNewServerData(NewServerResponseEvent e) {
    refreshView();
}

everything works very smoothly as long as i just run it while developing on my testing device. as soon as i build a release version and put that into the play store, that handleNewServerData() function is never called.

i can't make sense out of this. what differnce does it make to run that whole thing as a release build? is there maybe stuff happening in another thread that cant post to my subscriber?

can someone point me into the right direction?

thanks in advance


Answer:

Chances are that your release build is run through ProGuard and it deduces that since the subscriber methods are not directly called, they can be safely removed as unused code. Otto invokes the methods via reflection and ProGuard cannot see that.

Add the following to your proguard config file to keep methods annotated with @Subscribe or @Produce:

-keepattributes *Annotation*
-keepclassmembers class ** {
    @com.squareup.otto.Subscribe public *;
    @com.squareup.otto.Produce public *;
}

Question:

I'm using a setup in which every Presenter that is a retained Fragment has its own Realm instance. However, this essentially means that these Realms are all on the main thread.

Now that also means, if I want to modify the Realm, I either need to do that on the main thread (which is okay for small data sets, but I don't really want to do that with large data sets), or I need to do it on a background thread, and refresh every Realm instance at once (which is possible with a simple event to the event bus).

public enum SingletonBus {
    INSTANCE;

    private static String TAG = SingletonBus.class.getSimpleName();

    private Bus bus;

    private boolean paused;

    private final Vector<Object> eventQueueBuffer = new Vector<>();

    private Handler handler = new Handler(Looper.getMainLooper());

    private SingletonBus() {
        this.bus = new Bus(ThreadEnforcer.ANY);
    }

    public <T> void postToSameThread(final T event) {
        bus.post(event);
    }

    public <T> void postToMainThread(final T event) {
        try {
            if(paused) {
                eventQueueBuffer.add(event);
            } else {
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            bus.post(event);
                        } catch(Exception e) {
                            Log.e(TAG, "POST TO MAIN THREAD: BUS LEVEL");
                            throw e;
                        }
                    }
                });
            }
        } catch(Exception e) {
            Log.e(TAG, "POST TO MAIN THREAD: HANDLER LEVEL");
            throw e;
        }
    }

    public <T> void register(T subscriber) {
        bus.register(subscriber);
    }

    public <T> void unregister(T subscriber) {
        bus.unregister(subscriber);
    }

    public boolean isPaused() {
        return paused;
    }

    public void setPaused(boolean paused) {
        this.paused = paused;
        if(!paused) {
            Iterator<Object> eventIterator = eventQueueBuffer.iterator();
            while(eventIterator.hasNext()) {
                Object event = eventIterator.next();
                postToMainThread(event);
                eventIterator.remove();
            }
        }
    }
}

And

SingletonBus.INSTANCE.postToMainThread(new RealmRefreshEvent());

@Subscribe
public void onRealmRefreshEvent(RealmRefreshEvent e) {
    this.realm.refresh();
}

But assuming I have about 5-7 realm instances open on the main thread (as every presenter has its own open realm while they are not destroyed), I'm concerned about performance and/or memory usage.

So I guess I have two questions,

1.) Is it bad practice / heavily resource-intensive to have multiple Realm instances open on the main thread?

2.) How resource-intensive is it to update multiple Realms on the same thread with a global refresh event?


Answer:

Realm uses a ThreadLocal cache internally pr. Realm file so it is practically free to call Realm.getInstance() in every activity/fragment/presenter you have. The first call to Realm.getInstance() will cost a little as database has to be opened and the schema verified, but after that it just cost a cache lookup.

The cache is reference counted so the native resources will only be freed after all instances has been closed. This means it can be beneficial to keep at least one open instance around as long as possible.

This also means that when you update 1 of you open instances, they all get updated automatically.