英文:
Wrong order of commands execution in TimerTask.run() (in Android app written in Java)
问题
我正在编写一个简单的Android应用程序(使用Java),应能够加载一个*.txt文件,并以每0.5秒显示其内容的方式逐字显示。我已经成功将文本文件的内容放入一个String数组中,其中每个索引存储一个单词。我使用Timer和TimerTask,在给定的时间间隔内从数组中显示单词到一个TextView中。为了实现这个目标,我的TimerTask的run()方法首先调用TextView.setText()方法,该方法显示数组中当前索引处的项目,然后递增索引(至少我是这样认为的)。事实证明首先递增索引,然后才调用TextView.setText()方法(尽管在TimerTask的重写run()方法中命令的顺序不同)。我的问题是:为什么命令以这个顺序执行,我该如何修复它?
TimerTask readingTimerTask = new TimerTask() {
@Override
public void run() {
if (wordIndex < textArr.length - 1) {
runOnUiThread(new Runnable() {
@Override
public void run() {
tvWords.setText(textArr[wordIndex]);
Log.i(LOG_TAG, "setText() called. wordIndex: " + String.valueOf(wordIndex));
}
});
wordIndex++;
Log.i(LOG_TAG, "wordIndex incremented. New value of wordIndex: " + String.valueOf(wordIndex));
} else {
stopReading();
}
}
};
mTimer = new Timer();
Log.i(LOG_TAG, "Timer is starting. wordIndex: " + String.valueOf(wordIndex));
mTimer.schedule(readingTimerTask, 100, displayTimeInMs);
而Logcat看起来是这样的:
D/MainActivity: onActivityResult中的wordIndex(已加载文件):0
I/MainActivity: 计时器正在启动。wordIndex:0
I/MainActivity: wordIndex已增加。wordIndex的新值:1
I/MainActivity: 调用setText()。wordIndex:1
I/MainActivity: wordIndex已增加。wordIndex的新值:2
I/MainActivity: 调用setText()。wordIndex:2
I/MainActivity: wordIndex已增加。wordIndex的新值:3
I/MainActivity: 调用setText()。wordIndex:3
I/MainActivity: wordIndex已增加。wordIndex的新值:4
I/MainActivity: 调用setText()。wordIndex:4
I/MainActivity: wordIndex已增加。wordIndex的新值:5
I/MainActivity: 调用setText()。wordIndex:5
I/MainActivity: 计时器已停止。wordIndex:5
英文:
I’m writing a simple android app (in Java) that should be able to load a *.txt file and display its content one word at a time every 0.5 second. I’ve managed to put content of a text file into an String array which under each index stores one word. I’m using Timer and TimerTask to display words from the array in a TextView with given interval. To achieve this goal my TimerTask run() method first calls TextViev.setText() method that displays item from the array at current index and then increments index (or so I thought). As it turns out first the index is incremented and then TextViev.setText() method is called (despite the order of commands in overridden run() method of TimerTask). My question is: why commands are executed in this order, and how can I fix it?
TimerTask readingTimerTask = new TimerTask() {
@Override
public void run() {
if (wordIndex < textArr.length - 1) {
runOnUiThread(new Runnable() {
@Override
public void run() {
tvWords.setText(textArr[wordIndex]);
Log.i(LOG_TAG, "setText() called. wordIndex: " + String.valueOf(wordIndex));
}
});
wordIndex++;
Log.i(LOG_TAG, "wordIndex incremented. New value of wordIndex: " + String.valueOf(wordIndex));
} else {
stopReading();
}
}
};
mTimer = new Timer();
Log.i(LOG_TAG, "Timer is starting. wordIndex: " + String.valueOf(wordIndex));
mTimer.schedule(readingTimerTask, 100, displayTimeInMs);
And the Logcat looks something like this:
D/MainActivity: wordIndex in onActivityResult (file loaded): 0
I/MainActivity: Timer is starting. wordIndex: 0
I/MainActivity: wordIndex incremented. New value of wordIndex: 1
I/MainActivity: setText() called. wordIndex: 1
I/MainActivity: wordIndex incremented. New value of wordIndex: 2
I/MainActivity: setText() called. wordIndex: 2
I/MainActivity: wordIndex incremented. New value of wordIndex: 3
I/MainActivity: setText() called. wordIndex: 3
I/MainActivity: wordIndex incremented. New value of wordIndex: 4
I/MainActivity: setText() called. wordIndex: 4
I/MainActivity: wordIndex incremented. New value of wordIndex: 5
I/MainActivity: setText() called. wordIndex: 5
I/MainActivity: Timer has been stopped. wordIndex: 5
</details>
# 答案1
**得分**: 0
**为什么要按这个顺序执行命令?**
当从后台线程调用`runOnUiThread()`时,它不会等待`run()`内的命令完成后才返回。它会安排这些命令在一个单独的线程上执行:UI 线程。
我假设你的代码片段在 UI(或“主”)线程上被调用。与此同时,你的`TimerTask`引入了第二个线程:它的内部后台工作者线程。换句话说,你创建了一个[竞态条件](https://en.wikipedia.org/wiki/Race_condition#Example)。
`wordIndex++`在这种情况下不是_保证_会首先执行,但通常会在竞争中获胜,因为 UI 线程需要一点时间来处理`Runnable`。
**如何修复这个问题?**
你可能并不打算在程序中引入多个线程 —— 那会使事情变得非常复杂,这并不适合新手程序员。你可能希望使用[`CountDownTimer`](https://developer.android.com/reference/android/os/CountDownTimer),而不是`TimerTask`。这将帮助你保持一切在 UI 线程上执行。
<details>
<summary>英文:</summary>
**Why are the commands executed in this order?**
When called from a background thread, `runOnUiThread()` does not wait for the commands within `run()` to complete before returning. It arranges for them to execute on a _separate thread_: The UI thread.
I assume that your code snippet is invoked on the UI (or "main") thread. Meanwhile, your `TimerTask` is introducing a second thread: Its internal background worker. IOW, you have created a [race condition](https://en.wikipedia.org/wiki/Race_condition#Example).
The `wordIndex++` is not _guaranteed_ to execute first, under such a condition, but it will usually "win" the race, since it takes a little while for the UI thread to handle the `Runnable`.
**How can I fix it?**
You might not've intended to introduce multiple threads into your program -- that can make things quite complicated, and it is not for novice programmers. You may want to use a [`CountDownTimer`](https://developer.android.com/reference/android/os/CountDownTimer), instead of a `TimerTask`. That will help you keep everything on the UI thread.
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论