Android: 你能在 DoInBackground 中访问 AsyncTask class 成员吗?

Android: can you access AsyncTask class members in DoInBackground?

运行 AsyncTask class 成员在 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()
    }

}

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

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

您确实可以在 doInBackground() 中访问 AsyncTask 的任何字段,条件是 mObjA 不是 UI 相关的 Android class 基本上像 ViewPagerLinearLayout 或任何类型的 ViewGroup

这个问题比较笼统,有几个部分,让我们一次一个地处理它们:

"Is it safe to run a [any] method of an AsyncTask class member [not further specified Object] inside doInBackground?"

关于什么安全吗?

"Is there a multithreading issue?"

好的,多线程安全吗?答案是否定的,它通常不安全,除非你知道这个特别方法在这个特别对象可以安全地从多个线程调用。换句话说,只是将某些东西放入 AsyncTask 并不比使用任何其他 Thread.

更安全

示例:

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

输出:

08:52:37.462: D/MainActivity(13051): Starting...
08:52:37.466: D/MyAsyncTask(13051): Starting...
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): Done!
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): Done!

哦,有事"weird"发生了。

让我们运行再说一遍:

08:53:44.551: D/MainActivity(13286): Starting...
08:53:44.562: D/MyAsyncTask(13286): Starting...
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): Done!
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): Done!

仍然很奇怪,但不同。

让我们再 运行 一次:

08:54:23.560: D/MainActivity(13286): Starting...
08:54:23.579: D/MyAsyncTask(13286): Starting...
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): Done!
08:54:24.423: D/MyAsyncTask(13286): Done!

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

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

来自问题:"should you in doInBackground only access objects which have been deliberately passed"?

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

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

当我们运行这个时会发生什么?

09:11:43.734: D/MainActivity(15881): Starting...
09:11:43.763: D/MyAsyncTask(15881): Starting...
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): Done!
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): Done!

哇,还是很奇怪。因此,如果我们将 Object 作为参数传递,AsyncTask 似乎甚至无法保护我们。

那么它保护我们免受什么伤害呢? docs 状态:

AsyncTask guarantees that all callback calls are synchronized in such a way that the following operations are safe without explicit synchronizations.
- Set member fields in the constructor or onPreExecute(), and refer to them in doInBackground(Params...).
- Set member fields in doInBackground(Params...), and refer to them in onProgressUpdate(Progress...) and onPostExecute(Result).

就是这样,没有空白支票。

那么我们该如何解决呢?

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

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

08:56:59.370: D/MainActivity(13876): Starting...
08:56:59.375: D/MyAsyncTask(13876): Starting...
08:57:00.287: D/MainActivity(13876): Done!
08:57:01.216: D/MyAsyncTask(13876): Done!

耶!终于!

您还可以同步整个方法:

private synchronized void testLoop(String logTag, SimpleDateFormat sdf, String inputString) {
    // ...
}

08:59:11.237: D/MainActivity(14361): Starting...
08:59:12.036: D/MainActivity(14361): Done!
08:59:12.036: D/MyAsyncTask(14361): Starting...
08:59:12.862: D/MyAsyncTask(14361): Done!

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

处理程序呢?

来自问题:"do you need to use a handler?"

让我们修改示例以使用 Handler:

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

10:36:15.899: D/MainActivity(17932): Starting...
10:36:16.028: D/MainActivity(17932): Done!
10:36:16.038: D/MyAsyncTask(17932): Starting...
10:36:16.115: D/MyAsyncTask(17932): Done!

A Handler 在这种情况下也能正常工作(尽管该示例现在开始看起来很差),因为它通过 运行ning 实质上消除了特定代码段的多线程问题完全在另一个线程上。这也意味着如果您需要立即在 AsyncTask 中使用那段代码的结果,这将不会真正起作用。

所以这些关于不在 doInBackground() 中使用 UI 元素的讨论是怎么回事?

新示例:

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

运行 这个,点击按钮,你的应用程序将停止,你会在 logcat 中找到以下堆栈跟踪:

11:21:36.630: E/AndroidRuntime(23922): FATAL EXCEPTION: AsyncTask #1
11:21:36.630: E/AndroidRuntime(23922): Process: com.example.testsothreadsafe, PID: 23922
11:21:36.630: E/AndroidRuntime(23922): java.lang.RuntimeException: An error occured while executing doInBackground()
11:21:36.630: E/AndroidRuntime(23922): at android.os.AsyncTask.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): at java.util.concurrent.FutureTask.run(FutureTask.java:242)
11:21:36.630: E/AndroidRuntime(23922): at android.os.AsyncTask$SerialExecutor.run(AsyncTask.java:231)
11:21:36.630: E/AndroidRuntime(23922): at 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): at java.lang.Thread.run(Thread.java:818)
11:21:36.630: E/AndroidRuntime(23922): Caused by: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
11:21:36.630: E/AndroidRuntime(23922): at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357)
11:21:36.630: E/AndroidRuntime(23922): at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:874)
11:21:36.630: E/AndroidRuntime(23922): at android.view.View.requestLayout(View.java:17476)
11:21:36.630: E/AndroidRuntime(23922): at android.view.View.requestLayout(View.java:17476)
11:21:36.630: E/AndroidRuntime(23922): at android.view.View.requestLayout(View.java:17476)
11:21:36.630: E/AndroidRuntime(23922): at android.view.View.requestLayout(View.java:17476)
11:21:36.630: E/AndroidRuntime(23922): at android.view.View.requestLayout(View.java:17476)
11:21:36.630: E/AndroidRuntime(23922): at android.widget.TextView.checkForRelayout(TextView.java:6871)
11:21:36.630: E/AndroidRuntime(23922): at android.widget.TextView.setText(TextView.java:4057)
11:21:36.630: E/AndroidRuntime(23922): at android.widget.TextView.setText(TextView.java:3915)
11:21:36.630: E/AndroidRuntime(23922): at 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): at android.os.AsyncTask.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 more

Android 专门防止您从创建它的线程之外的另一个线程操纵视图层次结构。但是任何访问都被阻止了吗?

将示例更改为:

@Override
protected Void doInBackground(Void... params) {
    Log.d("MyAsyncTask", tv.getText().toString());
    return null;
}

点击按钮,输出为:

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

看来,允许某些访问。

这里的信息是什么?多线程编程很棘手。仅仅因为 Android 阻止你做一些事情,这并不意味着它不会阻止你做的事情会自动 "safe".

AsyncTask 是线程安全的。但我想只使用 AsyncTask 的通用性质会更方便。这样你就可以通过匿名内部 类.

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

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

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