一个Android AsyncTask中的doInBackground方法能被同步化以串行执行任务吗?

10

是否可以将AsyncTask.doInBackground设置为同步,或以其他方式实现相同的结果?

class SynchronizedTask extends AsyncTask {

    @Override
    protected synchronized Integer doInBackground(Object... params) {
        // do something that needs to be completed 
        // before another doInBackground can be called
    }
}

在我的情况下,任何一个 AsyncTask.execute() 都可以在前一个任务完成之前启动,但我需要在前一个任务完成后才执行 doInBackground 中的代码。 编辑:正如正确指出的那样,同步仅在相同的对象实例上起作用。不幸的是,不可能在同一对象实例上创建一个 AsyncTask 并调用多次 execute(),正如 AsyncTask documentation 中的 "Threading rules" 部分所指定的那样。
解决方法是使用自定义 Executor 对任务进行序列化,或者如果您使用的是 API 11 或更高版本,则使用 AsyncTask.executeOnExecutor(),如下面的评论所建议的那样。
我发布了一个答案,展示了可以用于排队按顺序执行任务的SerialExecutor的实现。

1
这不是synchronized的意思!它意味着该函数(或块)在此对象上执行时不会与另一个线程交错。 - Bondax
1
是的,但在不同的AsyncTask实例上! - Bondax
1
如果您使用API级别11,您可以创建SingleThreadExecutor,并通过调用AsyncTask.executeOnExecutor()来启动您的AsyncTask,从而导致排队执行AsyncTask。 - yorkw
1
@Stallman,是的,自API Level 1以来就有这个API了,请查看Excutors.newSingleThreadExecutor() - yorkw
1
@Stallman:异步任务应该只由一个线程执行。不要手动调用doInBackground()。只有全局变量可以以关键方式访问,因此请确保对这些变量的访问是线程安全的。 "同步"的意思是:没有两个线程可以在该对象实例上执行该方法。因此,请再次注意:不要手动调用doInBackground()! - Bondax
显示剩余6条评论
4个回答

20

理想情况下,我希望能够使用AsyncTask.executeOnExecutor()与一个 SERIAL_EXECUTOR一起使用,但这只适用于API级别11或以上:

new AsyncTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, params);

为了支持Android API 11以下的版本,我实现了一个自定义类,该类封装了具有1个线程池大小的ExecutorService。代码已在此处开源。

Executors.newFixedThreadPool(int nThreads)创建一个线程池,该线程池重用一定数量的线程,并且使用共享无界队列。在任何时刻,最多只有nThreads个线程正在处理任务。在我的情况下,nThreads为1,这意味着任务可以排队,但是任何给定时间只会执行一个任务。

以下是代码:

public abstract class SerialExecutor {
    private final ExecutorService mExecutorService;

    public SerialExecutor() {
        mExecutorService = Executors.newFixedThreadPool(1);
    }

    public void queue(Context context, TaskParams params) {
        mExecutorService.submit(new SerialTask(context, params));
    }

    public void stop() {
        mExecutorService.shutdown();
    }

    public abstract void execute(TaskParams params);

    public static abstract class TaskParams { }

    private class SerialTask implements Runnable {
        private final Context mContext;
        private final TaskParams mParams;

        public SerialTask(Context context, TaskParams params) {
            mContext = context;
            mParams = params;
        }

        public void run() {
            execute(mParams);
            Activity a = (Activity) mContext;
            a.runOnUiThread(new OnPostExecute());
        }
    }

    /**
     * Used to notify the UI thread
     */
    private class OnPostExecute implements Runnable {

        public void run() {

        }
    }
}

这可以被扩展并用作在Activity中的串行任务执行器:

public class MyActivity extends Activity {
    private MySerialExecutor mSerialExecutor;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        // ...
        mSerialExecutor = new MySerialExecutor();
    }

    @Override
    protected void onDestroy() {
        if (mSerialExecutor != null) {
            mSerialExecutor.stop();
        }
        super.onDestroy();
    }

    public void onTrigger(int param) {
        mSerialExecutor.queue(this, new MySerialExecutor.MyParams(param));
    }

    private static class MySerialExecutor extends SerialExecutor {

        public MySerialExecutor() {
            super();
        }

        @Override
        public void execute(TaskParams params) {
            MyParams myParams = (MyParams) params;
            // do something...
        }

        public static class MyParams extends TaskParams {
            // ... params definition

            public MyParams(int param) {
                // ... params init
            }
        }
    }
}

在 new syncTask().executeOnExecutor(SERIAL_EXECUTOR, params); 中,SERIAL_EXECUTOR 是什么?param 是我的参数... - Karan Mavadhiya
一个 IntentService 是否是一个有效的选择?它可以异步运行作业并实现先进先出的作业队列。 - type-a1pha

5

你可能想考虑使用IntentService代替。由于它具有内置的排队功能,因此它似乎更适合你的流程。


谢谢,感谢提供这个好的替代方案,但我需要每个恢复的Activity都能独立执行串行任务。这就是为什么我需要一个类似于AsyncTask的每个Activity后台任务序列化器,而不是一个中央服务被所有Activity共享。 - Lorenzo Polidori
我认为,要想获得与您在此问题中提到的executeOnExecuter类似的结果,可以从AsyncTask中调用一个静态同步方法(这将锁定该类以防其他线程干扰),该方法包含您要处理的工作。然而,这种方法与executeOnExecutor一样,并不能保证线程的处理顺序。如果您对API 11的executeOnExecutor线程方式感到满意,则可以使用此过程,否则您应该排队处理线程。 - shibbybird
为了确保任务的顺序被保留,我实现了一个SerialExecutor类,它使用由Executors#newFixedThreadPool(1)创建的ExecutorService(请参见我对这个问题的回答)。从Executors#newFixedThreadPool(int nThreads)的文档中可以看出,该方法创建一个线程池,该线程池重用一定数量的线程,这些线程在共享的无界队列上运行。在任何时刻,最多只有nThreads个线程会处于活动状态,处理任务。如果在所有线程都处于活动状态时提交了额外的任务,则它们将在队列中等待,直到有线程可用。 - Lorenzo Polidori
你的答案看起来肯定会起作用。抱歉,当我写下这句话时,我刚醒来。另外,为了更好地回答你的评论,你可以将IntentService绑定到任何给定的Activity。那也应该会起作用。 - shibbybird

0
public class RestAsyncTask1 extends AsyncTask<String, Void, String> {

    private AsyncTaskCompleteListener callback;
    private Context context;
    private String method;
    private static final AtomicInteger PROGRESS_NUM = new AtomicInteger(0);
    private static ProgressDialog PROGRESS_DIALOG;

    public RestAsyncTask1(Context context, AsyncTaskCompleteListener callback, String method) {
        this.callback = callback;
        this.context = context;
        this.method = method;
    }

    public static String format(String url, String... params) {
        String[] encoded = new String[params.length];

        for (int i = 0; i < params.length; i++) {
            encoded[i] = Uri.encode(params[i]);
        }

        return String.format(url, (String[]) encoded);
    }

    @Override
    protected void onPreExecute() {
        int x = PROGRESS_NUM.getAndIncrement();

        if (x == 0) {
            String title = "M_yug";
            PROGRESS_DIALOG = new ProgressDialog(context);
           // PROGRESS_DIALOG.setTitle(title);
            PROGRESS_DIALOG.setIndeterminate(true);
            PROGRESS_DIALOG.setCancelable(false);
            PROGRESS_DIALOG.setOnCancelListener(null);
            PROGRESS_DIALOG.setMessage("Loading. Please wait...");
            PROGRESS_DIALOG.show();
        }
    }

    @Override
    protected String doInBackground(String... params) {
        String url = params[0];
        String response = null;
        HttpURLConnection connection = null;

        if (params.length > 1) {
            if (method.equals(Method.GET)) {
                url = format(url, (String[]) Arrays.copyOfRange(params, 1, params.length));
            } else if (params.length > 2) {
                url = format(url, (String[]) Arrays.copyOfRange(params, 1, params.length - 1));
            }

            try {
                URL call = new URL(url);
                connection = (HttpURLConnection) call.openConnection();
                connection.setRequestProperty("Content-Type", "application/json");
                //connection.setRequestProperty("M-Yug", Utilities.VERSION);
                connection.setRequestMethod(method);
                connection.setDoOutput(true);

                if (method.equals("POST")) {
                    BufferedOutputStream outputStream = new BufferedOutputStream(connection.getOutputStream());
                    outputStream.write(params[params.length - 1].getBytes());
                    outputStream.flush();
                }

                int status = connection.getResponseCode();

                if (status == HttpURLConnection.HTTP_OK) {
                    InputStream is = connection.getInputStream();
                    response = readValue(is);
                } else if (status == 400) {
                    InputStream is = connection.getErrorStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
                    StringBuilder builder = new StringBuilder();
                    String line;

                    while ((line = reader.readLine()) != null) {
                        builder.append(line);
                    }

                    reader.close();
                    Toast.makeText(context, "" + builder.toString(), Toast.LENGTH_SHORT).show();
                }

                connection.disconnect();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (connection != null) {
                    connection.disconnect();
                }
            }
        }

        return response;
    }

    @Override
    protected void onPostExecute(String s) {
        int x = PROGRESS_NUM.decrementAndGet();

        if (x == 0 && PROGRESS_DIALOG != null && PROGRESS_DIALOG.isShowing()) {
            PROGRESS_DIALOG.dismiss();
        }

        if (s!=null) {
            String resopnse=s.toString();
            callback.onSuccess(resopnse);
        } else {
           Toast.makeText(context,"Server Not Responding",Toast.LENGTH_SHORT).show();
        }
    }

    private String readValue(InputStream is) {
        BufferedReader br = null;
        StringBuilder sb = new StringBuilder();
        String line;

        try {
            br = new BufferedReader(new InputStreamReader(is));

            while ((line = br.readLine()) != null) {
                sb.append(line);
            }
        } catch (Exception e) {
        }

        return sb.toString();
    }

    enum Method {
        GET, POST
    }
}

-2

AsyncTask 用于在后台线程中运行代码,以避免当前进程被打断。

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
 protected Long doInBackground(URL... urls) {
     int count = urls.length;
     long totalSize = 0;
     for (int i = 0; i < count; i++) {
         totalSize += Downloader.downloadFile(urls[i]);
         publishProgress((int) ((i / (float) count) * 100));
     }
     return totalSize;
 }

 protected void onProgressUpdate(Integer... progress) {
     setProgressPercent(progress[0]);
 }

 protected void onPostExecute(Long result) {
     showDialog("Downloaded " + result + " bytes");
 }

}

首先,您的doInBackground函数被调用,并且返回的对象将移动到onPostExecute。 如果您想在某个过程之后运行哪些代码,请将其放入PostExecute函数中。 这肯定会帮助您


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接