Android并发编程之全方位解析AsyncTask

Android并发编程之全方位解析AsyncTask

标签: androidasynctask
  1595人阅读  评论(2)  收藏  举报
  分类:

目录(?)[+]

前言

这篇文章我不会直接去分析源码,因为有太多分析AsyncTask的源码的文章了,我再去分析一遍源码也没有意义,因此这篇文章我会根据问答的形式,提出问题,然后再到源码中寻找答案,这样可以将AsyncTask理解的更加透彻。

AsyncTask是串行执行还是并行执行?

首先先来看一个例子,然后再通过源代码来解释为什么是这样 
这里写图片描述

public class MainActivity extends AppCompatActivity {
    private TextView tv_task1;
    private TextView tv_task2;
    private TextView tv_task3;
    private TextView tv_task4;
    private TextView tv_task5;
    private List<TextView> mTextViews;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv_task1 = (TextView) findViewById(R.id.tv_task1);
        tv_task2 = (TextView) findViewById(R.id.tv_task2);
        tv_task3 = (TextView) findViewById(R.id.tv_task3);
        tv_task4 = (TextView) findViewById(R.id.tv_task4);
        tv_task5 = (TextView) findViewById(R.id.tv_task5);
        mTextViews = new ArrayList<TextView>();
        mTextViews.add(tv_task1);
        mTextViews.add(tv_task2);
        mTextViews.add(tv_task3);
        mTextViews.add(tv_task4);
        mTextViews.add(tv_task5);
        new MyAsyncTask(this,0).execute();
        new MyAsyncTask(this,1).execute();
        new MyAsyncTask(this,2).execute();
        new MyAsyncTask(this,3).execute();
        new MyAsyncTask(this,4).execute();
    }

    private static class MyAsyncTask extends AsyncTask<Void,Void,Void>{
        private WeakReference<Activity> activityRef;
        private MainActivity mActivity;
        private int id;
        public MyAsyncTask(Activity activity , int id){
            activityRef = new WeakReference<Activity>(activity);
            mActivity = (MainActivity) activityRef.get();
            this.id = id;
        }

        @Override
        protected void onPreExecute() {
            mActivity.mTextViews.get(id).setText("task"+id+"正在执行...");
        }

        @Override
        protected Void doInBackground(Void... params) {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            mActivity.mTextViews.get(id).setText("task"+id+"执行完毕"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        }
    }

}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

我们发现,我们通过execute来启动AsyncTask的话,他是串行执行的。我们先不解释原因,我们再来看看下面这种情况 
这里写图片描述

new MyAsyncTask(this,0).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        new MyAsyncTask(this,1).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        new MyAsyncTask(this,2).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        new MyAsyncTask(this,3).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
        new MyAsyncTask(this,4).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

我们改为用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)来执行的话,就变成两个两个并行执行了。所以说,AsyncTask即可以串行执行,又可以并行执行,关键是看你怎么启动他,那么我们就来看一看源码,看看这其中的奥秘。

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }
   
   
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

我们调用execute启动AsyncTask后,这里面会间接调用executeOnExecutor方法,只不过是里面传的参数不一样而已,因此我们可以得出结论,不管怎样启动一个AsyncTask,最终都会调用executeOnExecutor方法来启动AsyncTask,只不过传入的参数一个是sDefaultExecutor,另一个是THREAD_POOL_EXECUTOR,那么其中的差别肯定是这两个参数的原因咯,我们先来分析一下THREAD_POOL_EXECUTOR是什么

public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
            TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
   
   
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

我们发现THREAD_POOL_EXECUTOR就是一个线程池,如果对线程池不了解的可以看一下这篇文章Android性能优化之使用线程池处理异步任务,我们来看一下这几个参数是什么: 
CORE_POOL_SIZE是核心线程数量,他定义的是当前设备的处理器个数+1

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
   
   
  • 1
  • 2
  • 1
  • 2

MAXIMUM_POOL_SIZE是最大线程数量,他定义的是当前设备的处理器个数*2+1

private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
   
   
  • 1
  • 1

KEEP_ALIVE是1,单位是TimeUnit.SECONDS,因此非核心线程的保活时间为1s

private static final int KEEP_ALIVE = 1;
   
   
  • 1
  • 1

sPoolWorkQueue是任务队列,它定义的是一个长度为128的阻塞队列,也就是说这个线程池的任务队列中可以同时有128个任务等待执行

private static final BlockingQueue<Runnable> sPoolWorkQueue =
            new LinkedBlockingQueue<Runnable>(128);
   
   
  • 1
  • 2
  • 1
  • 2

sThreadFactory是线程工厂,主要是用来new线程的

private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我们现在来看executeOnExecutor方法,如果我们传入的是THREAD_POOL_EXECUTOR线程池,那么在exec.execute方法中调用的就是THREAD_POOL_EXECUTOR的execute方法,因此任务就会在这个线程池中运行,由于我用的模拟器是1核CPU,因此同时会有2个任务并行执行。

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
                                                                       Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;

        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

我们再来看看sDefaultExecutor是什么

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
   
   
  • 1
  • 1

看名字就知道了,他肯定是一个串行的线程池

public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
   
   
  • 1
  • 1

我们来看看SerialExecutor是怎么设计的

private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

我们看到它里面维持了一个双端队列,这个双端队列在作为栈和队列使用时他的效率是很高的,所以这里使用双端队列来作为队列使用,我们看到他模拟了一个排队的过程,当有任务到来时,就把任务入队,然后在scheduleNext中出队一个任务,然后放到THREAD_POOL_EXECUTOR里去执行,虽然是放到THREAD_POOL_EXECUTOR去执行了,可是一次只放一个任务啊,因此还是串行执行的,所以这个SerialExecutor的作用就是将任务排好队,一个一个放入线程池中去执行,所以我们直接调用execute开启一个AsyncTask的时候,任务是串行执行的。

为什么AsyncTask不适合执行长时间耗时操作?

首先我们只说AsyncTask不适合执行长时间耗时操作,并没说他不能执行长时间耗时操作,通过上面的分析,我们知道AsyncTask是通过线程池来执行任务的,因此他是有能力执行长时间耗时操作的,但是我们为什么说他不适合执行呢?

  1. 我们要明白AsyncTask只是封装了Handler和Thread的一个工具类,他的目的就是让我们去工作线程拿到数据,然后通过handler切到主线程来更新UI,既然是更新UI,那肯定是越快越好啦,总不能让用户在一个页面等3分钟来等你刷新UI吧,那用户可能早就把页面关了。

  2. 如果我们使用execute()来启动一个AsyncTask的话,那么他默认是串行执行的,AsyncTask1需要执行10秒钟,AsyncTask2就需要等10秒钟,等AsyncTask1执行完了再执行,AsyncTask2再执行10秒钟,那么这个页面所有UI显示完毕需要20秒钟。如果其他的页面还有20个AsyncTask还没有执行完呢,那么本页面的AsyncTask1还要等前面20个AsyncTask执行完他再去执行,这肯定更不能接受了。因此AsyncTask不适合执行长时间的耗时任务。当然我们可以适当的使他们并发执行,但这也要在理解了并发编程与AsyncTask原理后才可以正确使用,所以这并不能否认AsyncTask不适合执行长时间耗时操作。

AsyncTask一定要在UI线程执行吗?

注意:我所分析的源代码是API 23中的AsyncTask源代码,其实这里主要是告诉大家一个分析过程,google为什么要让我们在UI线程中创建并且启动一个AsyncTask。通过源代码我们可以找到本标题的答案,大家可以通过以下步骤查看一下自己的源代码

我们先来看一个例子,这个例子中我们在一个工作线程中去创建并且启动一个AsyncTask,并且在onPreExecute和onPostExecute方法中给TextView赋值

public class MainActivity extends AppCompatActivity {
    private TextView tv_task1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv_task1 = (TextView) findViewById(R.id.tv_task1);

        new Thread(){
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                    MyAsyncTask task1 = new MyAsyncTask(MainActivity.this);
                    task1.execute();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    private static class MyAsyncTask extends AsyncTask<Void,Void,Void>{
        private WeakReference<Activity> activityRef;
        private MainActivity mActivity;
        public MyAsyncTask(Activity activity){
            activityRef = new WeakReference<Activity>(activity);
            mActivity = (MainActivity) activityRef.get();
        }

        @Override
        protected void onPreExecute() {
            mActivity.tv_task1.setText("task1正在执行");
        }

        @Override
        protected Void doInBackground(Void... params) {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            mActivity.tv_task1.setText("task1执行完毕");
        }
    }
}

   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

运行一下看看有什么问题 
这里写图片描述 
我们看到是在onPreExecute中崩的,说明onPreExecute是运行在工作线程了,而工作线程是不能更新UI的,所以抛出了异常,那么现在我们把onPreExecute注释掉,其他的地方不变,我们再运行一下,看看会出现什么结果 
这里写图片描述 
我们发现正常运行了,也就是说如果我们在工作线程中创建并且执行一个AsyncTask的话,onPreExecute是运行在工作线程的。因此我们可以得出一个结论:在工作线程中可以创建并且启动一个AsyncTask,并且onPreExecute是执行在工作线程的,onPostExecute是执行在UI线程的,通过查看源码,onProgressUpdate也是运行在主线程的,下面我们会分析源码。

我们一般都会在onPreExecute中显示一个dialog显示正在加载中,那如果我们在工作线程中创建并且启动一个AsyncTask的话,我们就无法显示这个dialog了。

下面我们来看一下AsyncTask中InternalHandler的源码,我们就可以很清楚的明白上面的结论了

private static class InternalHandler extends Handler {
        //关键在这里,当我们创建Handler的时候,这里默认绑定了
        //主线程的Looper,因此这个Handler发送的消息会发送到
        //主线程中
        public InternalHandler() {
            super(Looper.getMainLooper());
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        //我们知道Handler关联了主线程的Looper,那么他发送的消息会
        //发送到主线程的MessageQueue中,因此这个handleMessage
        //也是运行在主线程的
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                //发送result到onPostExecute
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                //调用onProgressUpdate并且传递过去进度值
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

因此我们发现onPostExecute和onProgressUpdate不管AsyncTask在哪里创建和启动,他们两个都会运行在UI线程中。 
我们再来看一下onPreExecute方法

 public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
                                                                       Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;
        //在这里调用onPreExecute
        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

当我们调用execute()后,execute()又会调用executeOnExecutor(),在executeOnExecutor中会调用onPreExecute方法,因此我们在哪个线程调用execute(),onPreExecute就会运行在哪个线程。

这下大家应该都明白了,通过源码我们已经证实了上面的结论

为什么同一个AsyncTask任务只能执行一次?

通过源码我们看到,AsyncTask为我们做出了如下的限制

 public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        //如果当前状态不是待执行状态
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING://如果是正在执行的状态
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED://如果是已执行完的状态
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;

        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

如果一个任务是正在执行状态或者已经执行完的状态,再次调用execute()的话就会抛出异常,但是AsyncTask为什么要给我们做出这种限制呢?

  1. AsyncTask如果用execute()方法直接来执行,默认是串行执行的,是一个任务执行完再执行下一个任务,这样倒是不会出现线程安全问题,但是我们考虑一下,如果我们执行完一个任务,里面的一些数据已经改变,当我们再次执行一遍这个任务,那么里面的数据肯定不是我们所期望的结果,因此我们还要再做个类似reset的操作,然而这并没有什么意义,如果真是这样,我们直接再new一个出来去执行不就完了么。

  2. AsyncTask可以使用executeOnExecutor()执行,里面传入AsyncTask.THREAD_POOL_EXECUTOR 就可以实现并发执行的效果了,这个我们前面已经详细说过了,所以为了模拟一个可以重复执行的AsyncTask,我们就开启一个线程池来执行一个任务,我们将这个任务执行3次,线程池会把这个任务分配给3个线程来并发执行此任务,这3个线程操作的是一个共享变量,我们看看会出现什么问题。

public class MainActivity extends Activity {

    private ExecutorService es;
    private MyRunnable mWorker;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //创建一个线程池
        es = Executors.newFixedThreadPool(10);
        //创建一个任务
        mWorker = new MyRunnable();
        //执行3次这个任务,线程池会将这3个任务分配给3个线程来并发执行,而我们操作的是一个共享变量
        es.execute(mWorker);
        es.execute(mWorker);
        es.execute(mWorker);
    }

    public class MyRunnable implements Runnable{
        private int num;
        @Override
        public void run() {
            for(int i=1 ; i<101 ; i++){
                try {
                    //每执行一次计算后就睡2毫秒,让效果明显
                    TimeUnit.MILLISECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                num++;
            }
            Log.i("zhangqi", Thread.currentThread().getName()+"执行num="+num);
        }
    }
}
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

这里写图片描述 
我们看到这里出现了问题,正确的结果应为num=300,这里出现了线程并发的问题,出现此问题的原因可以参考我另一篇文章 Android并发编程之图文解析volatile关键字

综合以上两点,如果AsyncTask不帮我们做出这样的限制,那么很多不理解并发编程的开发者会在编程中出现各种各样的错误,因此AsyncTask被设计为同一个对象只能执行一次

如何取消AsyncTask?

这里写图片描述

 /**
     * 取消异步任务
     * @param view
     */
    public void cancelAllTask(View view){
        for(AsyncTask task : mTasks){
            if (!task.isCancelled()){
                task.cancel(true);
            }
        }
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

我们可以通过isCanceled来判断当前任务是否被取消,如果没有被取消的话则调用cancel(true)方法立即停止当前的任务。当一个任务被取消后,他就不会执行到onPostExecute方法了,取而代之的是onCancelled方法,因此我们在onCancelled方法中更新了TextView的内容

@Override
        protected void onCancelled() {
            mActivity.mTextViews.get(id).setText("task" + id + "被取消于" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        }
   
   
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

接下来我们看看源码,看看我们调用了cancel后,他都做了些什么工作:

public final boolean cancel(boolean mayInterruptIfRunning) {
        mCancelled.set(true);
        return mFuture.cancel(mayInterruptIfRunning);
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

我们调用了cancel后,其实间接调用了mFuture的cancel方法,mFuture是什么呢?它其实是FutureTask,是在AsyncTask的构造方法中创建出来的,如果大家不理解FutureTask的话,可以看一下这篇文章Android并发编程之白话文详解Future,FutureTask和Callable

public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                return postResult(result);
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

当我们调用FutureTask的cancel方法后,他会抛出CancellationException异常,我们捕获到CancellationException异常后,会调用postResultIfNotInvoked(null)方法来使用Handler发送消息到onCancelled中,然后就会调用onCancelled方法而非onPostExecute方法了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值