在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
只访问故意传递的对象,还是可以在不使用处理程序的情况下自由访问该类的任何成员?
答案 0 :(得分:3)
您确实可以访问AsyncTask
内doInBackground()
的任何字段,条件是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);
但正如其他人所说,你在传递的对象可能不是是线程安全的。