绑定线程中断意外行为



Android ContentProvider使用"绑定线程"池来处理queryinsertcall等rpc。在我的ContentProvider实现中,我正在执行一个长时间运行的任务,该任务使用Thread#interrupt()是可中断的。我编写了一个看门狗线程,它在某些情况下会中断当前正在执行的绑定线程。中断是正确生效,但现在我注意到绑定线程仍然有它的中断标志设置时,它再次被android使用来处理一个新的RPC。这会导致新的RPC表现得好像被中断了一样。我没想到会这样。我捕获InterruptedException,但如果看门狗线程在没有任何查看或清除标志的情况下将中断标志设置到RPC的末尾,则可能发生这种情况。

ExecutorService的一些特别测试表明,当它重用线程来处理任务时,它在运行每个任务之前的某个时间重置中断标志。我在ThreadPoolExecutor类中发现了这个评论:

 * 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.

纠正android的行为,我认为在每个ContentProvider RPC的开始,我用Thread.interrupted()清除中断标志,但这似乎不是最优雅的解决方案。

是否有人可以确认android绑定线程实际上是处理线程中断不同于ExecutorService,以及什么可能是最好的工作,所以中断标志不是在不同的rpc之间传播?

下面是一些可以放在ContentProvider中的代码来重现这个问题:

@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;
}

然后在Activity中(确保它运行在与ContentProvider不同的进程中!)连接两个按钮,一个执行常规调用,一个执行将触发中断的调用:

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);
  }
});

这是我目前为止最好的解决这个问题的方法:

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是指向当前正在运行的线程的指针,我可能会中断该线程。

最新更新