Java 使线程等待()直到UI输入事件
我正在使用Android开发环境 我有线程在后台运行,具有复杂的for循环结构。对于For循环的每一次运行,我都希望听取一些UI输入,收集结果并继续For循环。为了使问题更简单,请查看以下代码:Java 使线程等待()直到UI输入事件,java,android,multithreading,Java,Android,Multithreading,我正在使用Android开发环境 我有线程在后台运行,具有复杂的for循环结构。对于For循环的每一次运行,我都希望听取一些UI输入,收集结果并继续For循环。为了使问题更简单,请查看以下代码: @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activ
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
mLeftText = (TextView) findViewById(R.id.leftText);
mVerboseText = (TextView) findViewById(R.id.verbose);
LoopTask taskA = new LoopTask();
Thread a = new Thread(taskA);
a.start();
}
class LoopTask implements Runnable {
@Override
public void run() {
synchronized (mSync) {
mLeftText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
doSomething();
mSync.notify();
}
});
for (int i = 0; i < 10; i++) {
try {
mSync.wait();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
}
我知道我一定是做错了什么,因为它向我抛出了异常:java.lang.IllegalMonitorStateException:在notify之前未被线程锁定的对象
我之所以尝试这种复杂的结构,只是因为我的循环结构非常复杂,我无法使用某些数据结构来记录循环的进度,并在触发事件后继续
请让我知道如何解决它,如果有任何替代解决方案是欢迎的
谢谢代码的问题在于onClickListener方法是由外部线程执行的,这与您创建的线程不同,后者实际上持有mSync上的锁。因此,当它收到通知时,它会抱怨没有锁。您可以通过使用0初始化的信号量实现同样的功能。您的代码如下所示:
class LoopTask implements Runnable {
private Semaphore s = new Semaphore(0);
@Override
public void run() {
mLeftText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
doSomething();
s.release();
}
});
for (int i = 0; i < 10; i++) {
try {
s.acquire();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
不过,信号量和notify/wait之间有一个区别:当您调用notify并且等待时没有线程阻塞时,该调用将丢失,它不会累积,因此下一个等待调用不会阻塞。对于信号量,如果调用release一次,那么下一个acquire调用将不会阻塞
我希望这能有所帮助。首先,你必须记住以下两件事: 您不能让UI线程等待 您不能从UI线程外部操作UI 对于那些认为:
你在自相矛盾。。。不要让UI线程等待和 然后将一个同步块编码到UI线程中?应该是 好的,如果这两个是对它的唯一访问,但是它是一个可能的 危险->应用程序未响应 我不会让我的UI等待,请仔细查看我的代码,但请注意,当线程调用等待时,它会释放lockmSync。而且在我看来,4月份是不可能的,你可以在其他答案下面阅读我的评论
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment()).commit();
}
}
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
public class MonitorObject{
}
final MonitorObject mSync = new MonitorObject();
public PlaceholderFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container,
false);
TextView txt = (TextView) rootView.findViewById(R.id.textview1);
txt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
synchronized (mSync) {
mSync.notify();
}
});
LoopTask taskA = new LoopTask();
Thread a = new Thread(taskA);
a.start();
return rootView;
}
class LoopTask implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (mSync) {
try {
mSync.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
final int runNumber = i;
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getActivity(), "inside the thread1 " + runNumber , Toast.LENGTH_SHORT).show();
}
});
}
}
}
}
}
和xml文件:
activity_main.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.thread_notify_wait.MainActivity"
tools:ignore="MergeRootFrame" />
fragment_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="16dip"
android:paddingLeft="16dip"
android:paddingRight="16dip"
android:paddingTop="16dip"
tools:context="com.example.thread_notify_wait.MainActivity$PlaceholderFragment" >
<TextView
android:id="@+id/textview1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="mmlooloo: Hello TypingPanda" />
</RelativeLayout>
最好的解决方案是使用信号量和runOnUiThread来读取UI上的任何更改 一个重要的补充:如果您想使用synchronize,请注意同步调用可能会阻塞。即使后台线程只调用synchronized s{s.wait;},在获得锁之后,但在使用wait再次释放之前,线程可能会由于各种原因暂停/挂起。在这种情况下,UI线程将阻塞,UI将挂起,直到后台线程再次激活并获得释放锁的执行时间。由于Android上的Dalvik VM将Java线程实现为本机linux线程,因此它们具有不同的优先级并争夺相同的系统资源。由于在重载系统上可能有许多线程的优先级高于后台线程并发GC、ui线程、系统线程,因此后台线程可能非常慢,因为它获得的资源非常少,而ui线程将获得高优先级,并且调整为经常运行。在最坏的情况下,UI线程将挂起,等待后台线程释放锁超过5秒,并触发“应用程序无响应” 因此,解决这个问题的方法是:在单独的线程上调用synchronized{s.notify},或者使用一个信号量,该信号量将通过对semaphore.release的两步非阻塞同步本机处理这些问题
private Semaphore s = new Semaphore(0);
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
final Thread a = new Thread() {
@Override
public void run() {
loopTask();
}
}.start();
TextView txt = (TextView) rootView.findViewById(R.id.textview1);
txt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// s.release() will not Block the UI-Thread
// even if Background-Task is suspended
// Because of Semaphore two-step implementation
s.release();
}
});
return rootView;
}
public void loopTask() {
for (int i = 0; i < 10; i++) {
try {
s.acquire();
final int runNumber = i;
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Any UI Action happens here!!!
Toast.makeText(getActivity(), "Running Loop " + runNumber , Toast.LENGTH_SHORT).show();
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我想要实现的目标是,我想要一个线程,ThreadA,它不同于UIThread,因为我想要在这个ThreadA中保持forloop,但我知道我不应该保持UIThread。对于ThreadA中forloop内的每次运行,我希望UIThread提供一个UI输入,一旦获得输入,我希望在下次运行时继续循环。我怎么能做到呢?嗯,对不起,我可能没有解释清楚。我想在主后台线程中保留forloop结构。只有在用户输入完成后才唤醒线程。试想一下readline是如何为普通的控制台程序工作的。感谢新的解决方案!!你在自相矛盾。。。不要让UI线程等待,然后将同步块编码到UI线程中?如果这两个是访问它的唯一途径,应该没问题,但这可能是一个危险->应用程序没有响应。仔细检查,mmlooo的代码对我来说工作正常~非常感谢!爱德华多,谢谢你的回答。我厌倦了你的方法。只有当我不在LoopTask中操作UI时,它才会起作用。虽然我希望更新UI发生在loopTask内部或onClickListener内部,但这可能吗?@TypingPanda我在mmloolo的建议中编辑过,所有UI内容都需要发生在UI线程中,
所以你必须用runonuithread来包装它,我想在主后台线程中保留forloop结构。只有在用户输入完成后才唤醒线程。试想一下readline是如何为普通的控制台程序工作的。你能解释一下为什么使用它吗?正如你所说,它可能会创建一个线程,如果是这样,另一个线程可以无限期地锁定和阻塞,但android源代码不这么认为!!当我搜索时,我发现了很多例子,这是一个凌乱的请求队列。请告诉我为什么他们都在UI线程上使用同步块