Java – Multithreaded callback method

Multithreaded callback method… here is a solution to the problem.

Multithreaded callback method

I have a service in my application that allows clients to register to receive callbacks from that service. I’m trying to implement it so that when the client callback implementation is called, they can run asynchronously or sequentially on the service thread. The specific behavior of each callback depends on which method is used to register the client for the callback. Clients can register with registerClientForCallbacks to have the callback occur on the main thread, or they can register with registerClientForAsyncCallbacks to have the callback occur on a new thread.

The problem I’m having is that when I try to call the callback on a new thread, the thread blocks thread.run() until runnable completes, effectively eliminating my attempt at multithreading. I’ve posted my service code below (using Javadoc because I know it’s long). Can someone help me understand why the threads in callAllRegisteredClients aren’t running at the same time?

public class ExampleService extends Service {
    /**
     * A record of the clients which have registered to receive callbacks from this service
     * asynchronously.
     */
    private Set<ClientCallbacks> registeredClientsAsync;

/**
     * A record of the clients which have registered to receive callbacks from this service on the
     * service's main thread.
     */
    private Set<ClientCallbacks> registeredClientsMainThread;

@Override
    public void onCreate() {
        super.onCreate();
        registeredClientsAsync = new HashSet<ClientCallbacks>();
        registeredClientsMainThread = new HashSet<ClientCallbacks>();
    }

@Override
    public IBinder onBind(Intent intent) {
        return new LocalBinder();
    }

/**
     * A binder with the singular purpose of returning a reference to the service.
     */
    public class LocalBinder extends Binder {
        public ExampleService getService() {
            return ExampleService.this;
        }
    }

/**
     * Register this client to receive callback events from this service. Callback methods will
     * execute sequentially on the service's main thread. Consider using {@link
     * #registerClientForAsyncCallbacks(ClientCallbacks) registerClientForAsyncCallbacks} for
     * asynchronous callbacks.
     *
     * @param client the client to register, not null
     * @return true if the client is unregistered or has been
     * registered to receive callbacks asynchronously using {@link
     * #registerClientForAsyncCallbacks(ClientCallbacks) registerClientForAsyncCallbacks}, false
     * otherwise
     */
    public boolean registerClientForCallbacks(@NonNull ClientCallbacks client) {
        if (client == null) {
            throw new IllegalArgumentException("client cannot be null");
        }

 Attempt to remove from async callbacks set (may do nothing)
        registeredClientsAsync.remove(client);

return registeredClientsMainThread.add(client);
    }

/**
     * Register this client to receive callback events from this service. Callback methods will be
     * executed asynchronously on separate threads.
     *
     * @param client the client to register, not null
     * @return true if the client is unregistered or has been
     * registered to receive callbacks in the service's main thread using {@link
     * #registerClientForCallbacks(ClientCallbacks) registerClientForCallbacks}, false
     * otherwise
     */
    public boolean registerClientForAsyncCallbacks(@NonNull ClientCallbacks client) {
        if (client == null) {
            throw new IllegalArgumentException("client cannot be null");
        }

 Attempt to remove from async callbacks set (may do nothing)
        registeredClientsMainThread.remove(client);

return registeredClientsAsync.add(client);
    }

/**
     * Calls the {@link #ClientCallbacks ClientCallbacks} callback methods in all registered
     * clients. Clients registered for asynchronous callbacks will execute immediately, and
     * clients registered for sequential callbacks will be called in turn. Note that if the
     * callback methods block, then the service's main thread will block.
     */
    private void callAllRegisteredClients() {
         First start asynchronous callbacks
        for (ClientCallbacks client : registeredClientsAsync) {
            Thread t = new Thread(new CallbackRunnable(client), client.toString());
            t.run();
        }

 Finally start sequential callbacks
        for (ClientCallbacks client : registeredClientsMainThread) {
            client.onServiceEvent(this);
        }
    }

/**
     * Interface allows callback events to be delivered to registered server clients.
     */
    public interface ClientCallbacks {
        public void onServiceEvent(Service service);
    }

/**
     * A utility class which allows client callback methods to be executed asynchronously.
     */
    private class CallbackRunnable implements Runnable {
        /**
         * The client to receive the callbacks.
         */
        private final ClientCallbacks client;

/**
         * Creates a new CallbackRunnable.
         * @param client the client to receive the callback methods, not null
         */
        private CallbackRunnable(@NonNull ClientCallbacks client) {
            if (client == null) {
                throw new IllegalArgumentException("client cannot be null");
            }

this.client = client;
        }

@Override
        public void run() {
            android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
            client.onServiceEvent(ExampleService.this);
        }
    }

/*
     * Testing
     * TODO remove
     */
    public void activateCallbacks() {
        callAllRegisteredClients();
    }
}

Solution

You must use t.start() instead of t.run().

t.run() calls runnable’s run() method directly. t.start() starts a new thread and then calls the run() method.

Also, you should consider using Executor instead of directly creating and starting threads….

Related Problems and Solutions