Android:您可以在DoInBackground中访问AsyncTask类成员吗?

时间:2015-03-06 16:16:08

标签: java android multithreading android-asynctask android-handler

AsyncTask内运行DoInBackground类成员的方法是否安全?或者你需要使用处理程序吗?

private class MyAsyncTask extends AsyncTask<Void, Void, Void> {

    Object mObjA = null:

    private MyAsyncTask(Object objA) {
        mObjA = objA
    }

    protected void doInBackground(Void... params) {
        mObjA.aMethod()
    }

}
doInBackground中的

如果mObjA.aMethod()未作为参数传递,则运行mObjA是否安全?是否存在多线程问题?

你应该doInBackground只访问故意传递的对象,还是可以在不使用处理程序的情况下自由访问该类的任何成员?

3 个答案:

答案 0 :(得分:3)

您确实可以访问AsyncTaskdoInBackground()的任何字段,条件是mObjA不是与UI相关的Android类,如ViewPager或{{1基本上是LinearLayout或任何类型的ViewGroup

答案 1 :(得分:2)

这个问题非常通用,而且有一些部分,让我们一次处理一个:

&#34;在AsyncTask内运行[{1}}班级成员[未进一步指定Object]的[任意]方法是否安全?&#34;

关于什么是安全的吗?

&#34;是否存在多线程问题?&#34;

好的,关于多线程也是安全的吗?答案是否定的,通常不安全,除非您知道特定对象上的特定方法可以安全地从多个线程调用。换句话说,只是将某些内容放入doInBackground并不会比使用任何其他AsyncTask更安全。

示例:

Thread

输出:

  

08:52:37.462:D / MainActivity(13051):开始......
  08:52:37.466:D / MyAsyncTask(13051):开始...
  08:52:37.466:D / MainActivity(13051):i:3 inputString:2015-04-01 23:23:23.232 outputString:2014-12-01 0012:34:23.789
  08:52:37.467:D / MainActivity(13051):完成!
  08:52:37.467:D / MyAsyncTask(13051):i:0 inputString:2014-12-24 12:34:56.789 outputString:2014-12-01 12:34:23.789
  08:52:37.467:D / MyAsyncTask(13051):完成了!

哦,某事&#34;很奇怪&#34;发生了。

让我们再次运行它:

  

08:53:44.551:D / MainActivity(13286):开始......
  08:53:44.562:D / MyAsyncTask(13286):开始...
  08:53:44.563:D / MainActivity(13286):i:11 inputString:2015-04-01 23:23:23.232 outputString:1970-01-24 12:00:23.232
  08:53:44.563:D / MainActivity(13286):完成!
  08:53:44.567:D / MyAsyncTask(13286):i:0 inputString:2014-12-24 12:34:56.789 outputString:2014-01-24 12:34:56.789
  08:53:44.567:D / MyAsyncTask(13286):完成了!

仍然很奇怪,但不同。

让我们再次运行它:

  

08:54:23.560:D / MainActivity(13286):开始......
  08:54:23.579:D / MyAsyncTask(13286):开始......
  08:54:23.596:D / MainActivity(13286):i:3 inputString:2015-04-01 23:23:23.232 outputString:2015-01-01 00:00:00.000
  08:54:23.596:D / MainActivity(13286):完成!
  08:54:24.423:D / MyAsyncTask(13286):完成了!

再次不同,这次它甚至没有在其中一个主题中表现出来。

绝对是一个多线程问题。我们该如何解决?

从问题:&#34;你是否应该在doInBackground中访问故意传递的对象&#34;?

好吧,让我们尝试一下,稍微修改一下代码:

public class MainActivity extends Activity {

    private void testLoop(String logTag, SimpleDateFormat sdf, String inputString) {
        Log.d(logTag, "Starting...");
        for (int i = 0; i < 100; i++) {
            try {
                String outputString;
                outputString = sdf.format(sdf.parse(inputString));
                if (!outputString.equals(inputString)) {
                    Log.d(logTag, "i: " + i + " inputString: " + inputString + " outputString: " + outputString);
                    break;
                }
            } catch (Exception e) {
                Log.d(logTag, "Boom! i: " + i, e);
                break;
            }
        }
        Log.d(logTag, "Done!");
    }

    public class MyAsyncTask extends AsyncTask<Void, Void, Void> {
        SimpleDateFormat sdf;
        public MyAsyncTask(SimpleDateFormat sdf) {
            this.sdf = sdf;
        }
        @Override
        protected Void doInBackground(Void... params) {
            testLoop("MyAsyncTask", sdf, "2014-12-24 12:34:56.789");
            return null;
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        new MyAsyncTask(sdf).execute();
        testLoop("MainActivity", sdf, "2015-04-01 23:23:23.232");
    }
}

我们运行时会发生什么?

  

09:11:43.734:D / MainActivity(15881):开始......
  09:11:43.763:D / MyAsyncTask(15881):开始...
  09:11:43.782:D / MainActivity(15881):i:7 inputString:2015-04-01 23:23:23.232 outputString:2015-004-01 00:00:00.789
  09:11:43.782:D / MainActivity(15881):完成!
  09:11:43.783:D / MyAsyncTask(15881):i:5 inputString:2014-12-24 12:34:56.789 outputString:2014-012-24 12:34:56.789
  09:11:43.783:D / MyAsyncTask(15881):完成了!

哇,还是很奇怪。因此,如果我们将public class MyAsyncTask extends AsyncTask<SimpleDateFormat, Void, Void> { @Override protected Void doInBackground(SimpleDateFormat... params) { testLoop("MyAsyncTask", params[0], "2014-12-24 12:34:56.789"); return null; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); new MyAsyncTask().execute(sdf); testLoop("MainActivity", sdf, "2015-04-01 23:23:23.232"); } 作为参数传递,AsyncTask似乎甚至无法保护我们。

那么它对我们有什么保护作用呢? docs州:

  

AsyncTask保证所有回调调用的同步方式使得以下操作在没有显式同步的情况下是安全的。
   - 在构造函数或onPreExecute()中设置成员字段,并在doInBackground(Params ...)中引用它们。
   - 在doInBackground(Params ...)中设置成员字段,并在onProgressUpdate(Progress ...)和onPostExecute(Result)中引用它们。

这就是全部,没有空白支票。

那么我们该如何解决呢?

有很多选择。在这个特别设计的示例中,显而易见的选择是不共享Object,而是在sdf中创建新实例。如果可能的话,这通常是一个不错的选择。如果没有,您(开发人员)必须确保访问以某种方式同步。对于我们的示例,您可以使用synchronized语句:

AsyncTask
  

08:56:59.370:D / MainActivity(13876):开始......
  08:56:59.375:D / MyAsyncTask(13876):开始...
  08:57:00.287:D / MainActivity(13876):完成!
  08:57:01.216:D / MyAsyncTask(13876):完成了!

耶!终于来了!

您还可以同步整个方法:

synchronized (sdf) {
    outputString = sdf.format(sdf.parse(inputString));
}
  

08:59:11.237:D / MainActivity(14361):开始......
  08:59:12.036:D / MainActivity(14361):完成!
  08:59:12.036:D / MyAsyncTask(14361):开始...
  08:59:12.862:D / MyAsyncTask(14361):完成了!

仍然有效,但现在你已基本上序列化了我们希望与多个线程并行完成的所有工作。不是不安全,但不是很好的表现。多线程编程的另一个缺陷,但与问题无关。 (事实上​​,上面的同步语句版本并没有更好的性能,因为我们在我们的例子中并没有真正做任何事情。)

处理程序怎么样?

从问题:&#34;你需要使用处理程序吗?&#34;

让我们修改示例以使用private synchronized void testLoop(String logTag, SimpleDateFormat sdf, String inputString) { // ... }

Handler
  

10:36:15.899:D / MainActivity(17932):开始......
  10:36:16.028:D / MainActivity(17932):完成!
  10:36:16.038:D / MyAsyncTask(17932):开始...
  10:36:16.115:D / MyAsyncTask(17932):完成!

在这种情况下,public class MyAsyncTask extends AsyncTask<Handler, Void, Void> { SimpleDateFormat sdf; public MyAsyncTask(SimpleDateFormat sdf) { this.sdf = sdf; } @Override protected Void doInBackground(Handler... params) { params[0].post(new Runnable() { @Override public void run() { testLoop("MyAsyncTask", sdf, "2014-12-24 12:34:56.789"); } }); return null; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); new MyAsyncTask(sdf).execute(new Handler()); testLoop("MainActivity3", sdf, "2015-04-01 23:23:23.232"); } 也可以正常工作(尽管现在看起来非常糟糕),因为它实际上通过在另一个线程上运行它来消除特定代码段的多线程问题。这也意味着如果您需要立即在Handler中使用该段代码的结果,这将无法真正发挥作用。

那么所有这些都是关于不使用AsyncTask中的UI元素的话题

新例子:

doInBackground()

运行此项,点击按钮,您的应用程序将停止,您将在logcat中找到以下堆栈跟踪:

  

11:21:36.630:E / AndroidRuntime(23922):致命异常:AsyncTask#1
  11:21:36.630:E / AndroidRuntime(23922):处理:com.example.testsothreadsafe,PID:23922
  11:21:36.630:E / AndroidRuntime(23922):java.lang.RuntimeException:执行doInBackground时发生错误()
  11:21:36.630:E / AndroidRuntime(23922):在android.os.AsyncTask $ 3.done(AsyncTask.java:304)
  11:21:36.630:E / AndroidRuntime(23922):at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355)
  11:21:36.630:E / AndroidRuntime(23922):at java.util.concurrent.FutureTask.setException(FutureTask.java:222)
  11:21:36.630:E / AndroidRuntime(23922):在java.util.concurrent.FutureTask.run(FutureTask.java:242)
  11:21:36.630:E / AndroidRuntime(23922):在android.os.AsyncTask $ SerialExecutor $ 1.run(AsyncTask.java:231)
  11:21:36.630:E / AndroidRuntime(23922):在java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
  11:21:36.630:E / AndroidRuntime(23922):at java.util.concurrent.ThreadPoolExecutor $ Worker.run(ThreadPoolExecutor.java:587)
  11:21:36.630:E / AndroidRuntime(23922):在java.lang.Thread.run(Thread.java:818)
  11:21:36.630:E / AndroidRuntime(23922):引起:android.view.ViewRootImpl $ CalledFromWrongThreadException:只有创建视图层次结构的原始线程才能触及其视图。
  11:21:36.630:E / AndroidRuntime(23922):在android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357)
  11:21:36.630:E / AndroidRuntime(23922):在android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:874)
  11:21:36.630:E / AndroidRuntime(23922):在android.view.View.requestLayout(View.java:17476)
  11:21:36.630:E / AndroidRuntime(23922):在android.view.View.requestLayout(View.java:17476)
  11:21:36.630:E / AndroidRuntime(23922):在android.view.View.requestLayout(View.java:17476)
  11:21:36.630:E / AndroidRuntime(23922):在android.view.View.requestLayout(View.java:17476)
  11:21:36.630:E / AndroidRuntime(23922):在android.view.View.requestLayout(View.java:17476)
  11:21:36.630:E / AndroidRuntime(23922):在android.widget.TextView.checkForRelayout(TextView.java:6871)
  11:21:36.630:E / AndroidRuntime(23922):在android.widget.TextView.setText(TextView.java:4057)
  11:21:36.630:E / AndroidRuntime(23922):在android.widget.TextView.setText(TextView.java:3915)
  11:21:36.630:E / AndroidRuntime(23922):在android.widget.TextView.setText(TextView.java:3890)
  11:21:36.630:E / AndroidRuntime(23922):at com.example.testsothreadsafe.MainActivity $ MyAsyncTask.doInBackground(MainActivity.java:22)
  11:21:36.630:E / AndroidRuntime(23922):at com.example.testsothreadsafe.MainActivity $ MyAsyncTask.doInBackground(MainActivity.java:1)
  11:21:36.630:E / AndroidRuntime(23922):在android.os.AsyncTask $ 2.call(AsyncTask.java:292)
  11:21:36.630:E / AndroidRuntime(23922):at java.util.concurrent.FutureTask.run(FutureTask.java:237)
  11:21:36.630:E / AndroidRuntime(23922):... 4更多

Android专门阻止您从另一个线程操纵视图层次结构,而不是创建它的那个线程。但是,是否阻止了任何访问

将示例更改为:

public class MainActivity extends Activity {
    private TextView tv;

    public class MyAsyncTask extends AsyncTask<Void, Void, Void> {
        TextView tv;
        public MyAsyncTask(TextView tv) {
            this.tv = tv;
        }
        @Override
        protected Void doInBackground(Void... params) {
            tv.setText("Boom!");
            return null;
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LinearLayout layout = new LinearLayout(this);
        tv = new TextView(this);
        tv.setText("Hello world!");
        Button button = new Button(this);
        button.setText("Click!");
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new MyAsyncTask(tv).execute();
            }
        });
        layout.addView(tv);
        layout.addView(button);
        setContentView(layout);
    }
}

单击按钮,输出将为:

  

11:25:20.950:D / MyAsyncTask(24329):Hello world!

看来,允许一些访问。

那么这里的信息是什么?多线程编程很棘手。仅仅因为Android阻止你做某些事情,这并不意味着它不会阻止你做任何事情是自动安全的。

答案 2 :(得分:0)

AsyncTask是线程安全的。但我想使用AsyncTask的通用性更方便。这样你只需要制作一个匿名的内部课程就可以了。

new AsyncTask<Object, Void, Void>() {

    protected Void doInBackground(Object... params){
        params[0].aMethod();
        return null;
    }
}.execute(obj);

但正如其他人所说,你在传递的对象可能不是是线程安全的。