Как ведут себя переменные экземпляра, передаваемые между потоками с помощью AsyncTask?
Я понимаю, что небезопасно получать доступ к переменной общего экземпляра между несколькими потоками (если переменная не объявлена volatile
и правильно synchronized
). Я пытаюсь понять семантику передачи переменной общего экземпляра в фоновый поток с помощью Android AsyncTask
,
Рассмотрим следующий код:
public class Example {
ContentValues contentValues;
public void start() {
contentValues = new ContentValues();
contentValues.put("one", 1);
new MyAsyncTask().execute(contentValues);
contentValues.put("two", 2);
}
class MyAsyncTask extends AsyncTask<ContentValues, Void, Boolean> {
@Override
public void onPreExecute() {
contentValues.put("three", 3);
}
@Override
protected Boolean doInBackground(ContentValues... cvs) {
ContentValues cv = cvs[0];
return cv == contentValues;
}
}
}
Что мы знаем о состоянии локальной переменной cv
в doInBackground()
? В частности,
Какие пары ключ-значение гарантированно будут в нем.
Какие пары ключ-значение могут быть в нем?
Что будет
doInBackground()
вернуть?
2 ответа
Если бы вы использовали базовый поток, поле члена не было бы синхронизировано, и видимость не будет гарантирована, как вы упомянули.
В случае использования AsyncTask это зависит от реализации инфраструктуры AsyncTask.
"one", 1
определенно будет там, потому что он помещается до создания потока.
Если мы проверим исходный код AsyncTask, мы сможем найти следующий комментарий:
* <h2>Memory observability</h2>
* <p>AsyncTask guarantees that all callback calls are synchronized in such a way that the following
* operations are safe without explicit synchronizations.</p>
* <ul>
* <li>Set member fields in the constructor or {@link #onPreExecute}, and refer to them
* in {@link #doInBackground}.
* <li>Set member fields in {@link #doInBackground}, and refer to them in
* {@link #onProgressUpdate} and {@link #onPostExecute}.
* </ul>
Так "three", 3
будет там, так как он был добавлен в onPreExecute
,
Также это означает, что ContentValues contentValues;
поле будет синхронизировано в точке doInBackground
, поэтому метод вернет true.
Хотя я не думаю, что "two", 2
Элемент гарантированно будет там, так как этот код выполняется параллельно с асинхронным потоком. Может быть, но не обязательно. На это могут влиять как гонка, так и аспекты видимости.
Какие пары ключ-значение гарантированно будут в нем? Какие пары ключ-значение могут быть в нем?
в
onPreExecute
,contentValues
будет иметь только одно значение, т.е.one=1
, Не будетtwo=2
, потому что ты.put("two", 2)
после вызова выполнить.в
doInBackground
,contentValues
буду иметьthree=3, two=2, one=1
потому что вы добавилиtwo=2 and three=3
или раньшеonPreExecute
,
Что возвратит doInBackground()?
doInBackground
фон вернется true
потому что очевидно cv == contentValues
(тот же пример)
В качестве доказательства вышеприведенных утверждений я изменил ваш Example
класс для печати некоторых сообщений на каждом этапе, чтобы вы могли понять состояние переменной экземпляра.
Example.kt
class Example {
internal lateinit var contentValues: ContentValues
fun start() {
contentValues = ContentValues()
contentValues.put("one", 1)
MyAsyncTask().execute(contentValues)
contentValues.put("two", 2)
}
internal inner class MyAsyncTask : AsyncTask<ContentValues, Void, Boolean>() {
public override fun onPreExecute() {
Log.d("TAG", "ContentValue in onPreExecute is $contentValues")
contentValues.put("three", 3)
}
override fun doInBackground(vararg cvs: ContentValues): Boolean? {
val cv = cvs[0]
Log.d("TAG", "ContentValue in doInBackground is $contentValues")
return cv == contentValues
}
override fun onPostExecute(result: Boolean?) {
Log.d("TAG", "Result is $result")
Log.d("TAG", "ContentValue in onPostExecute is $contentValues")
super.onPostExecute(result)
}
}
}
Выход
ContentValue in onPreExecute is one=1
ContentValue in doInBackground is three=3 two=2 one=1
Result is true
ContentValue in onPostExecute is three=3 two=2 one=1