Java 使线程等待()直到UI输入事件

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

我正在使用Android开发环境

我有线程在后台运行,具有复杂的for循环结构。对于For循环的每一次运行,我都希望听取一些UI输入,收集结果并继续For循环。为了使问题更简单,请查看以下代码:

    @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线程上使用同步块