2

Android ContentProviders use a pool of "binder threads" to process RPCs such as query, insert and call. In my implementation of a ContentProvider I am performing a long running task that is interruptible using Thread#interrupt(). I wrote a watchdog thread that interrupts the currently executing binder thread under certain circumstances. The interrupt is correctly taking effect but now I noticed that binder thread still has its interrupt flag set when it is used again by android to handle a new RPC. This causes the new RPC to behave as if it was interrupted. I did not expect this. I catch InterruptedException but this can happen if the watchdog thread sets the interrupted flag towards the end of the RPC when there is nothing looking at or clearing the flag.

Some ad-hoc testing of ExecutorService indicates that when it is reusing threads to handle tasks it resets the interrupt flag sometime before running each task. I found this comment in the ThreadPoolExecutor class:

 * 2. Before running any task, the lock is acquired to prevent
 * other pool interrupts while the task is executing, and
 * clearInterruptsForTaskRun called to ensure that unless pool is
 * stopping, this thread does not have its interrupt set.

To correct android's behavior I am thinking that at the beginning of each ContentProvider RPC I clear the interrupt flag with Thread.interrupted() but that doesn't seem like the most elegant solution.

Can anyone confirm if android binder threads are in fact handling thread interrupts differently from an ExecutorService, and what might be the best work-around so the interrupt flag isn't propagated between different RPCs?

Here is some code that can be placed in a ContentProvider to reproduce the issue:

@Override
public Bundle call(String method, String arg, Bundle extras) {
  Log.w(TAG, "Thread "+ Thread.currentThread().getName() + " before isInterrupted()=" + Thread.currentThread().isInterrupted());

  final Thread callThread = Thread.currentThread();

  if (method.equals("interrupt")) {
    new Thread("interrupter") {
      @Override
      public void run() {
        callThread.interrupt();
      }
    }.start();
  }

  try {
    Thread.sleep(500);
  } catch (InterruptedException e) {
    Log.w(TAG, e);
    Thread.currentThread().interrupt();
  }

  Log.w(TAG, "Thread "+ Thread.currentThread().getName() + " after isInterrupted()=" + Thread.currentThread().isInterrupted());

  return null;
}

Then in an Activity (make sure it is running in a different process from the ContentProvider!) hook up two buttons, one that does a regular call and one that does a call which will trigger an interrupt:

findViewById(R.id.call).setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View view) {
    getContentResolver().call(CallContentProvider.CONTENT_URI, "", "", null);
  }
});

findViewById(R.id.callint).setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View view) {
    getContentResolver().call(CallContentProvider.CONTENT_URI, "interrupt", "", null);
  }
});
4

1 回答 1

1

因此,这是我迄今为止解决此问题的最佳方法:

private volatile Thread mRunningCallThread;

@Override
public Bundle call(String method, String arg, Bundle extras) {
    Thread.interrupted(); // Clear interrupted flag

    mRunningCallThread = Thread.currentThread(); // Thread to interrupt

    // Do stuff, allowing interruption by another thread

    mRunningCallThread = null;

    Thread.interrupted(); // Clear interrupted flag
}

该字段mRunningCallThread是指向我可能会中断的当前正在运行的线程的指针。

于 2014-08-18T17:38:29.667 回答