使线程wait()直到UI输入事件

时间:2014-09-19 01:39:03

标签: java android multithreading

我正在使用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();
                    }
                }
            }
        }
    }

我知道我一定做错了,因为它让我异常:&#34; java.lang.IllegalMonitorStateException:对象在notify()&#34; 之前没有被线程锁定

我正在尝试这个复杂的结构,因为我的循环结构非常复杂,我无法使用某些数据结构来记录循环的进度并继续触发onClickEvent。

请告诉我如何修复它,以及是否欢迎任何替代解决方案。

由于

3 个答案:

答案 0 :(得分:1)

您的代码的问题是方法 onClickListener()由外部线程执行,该线程与您创建的线程(实际上持有mSync锁定的线程)不同。因此,当它到达 notify()时,它会抱怨没有锁定。您可以使用用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()上没有阻塞线程时,该调用是丢失,它不会累积,所以下一次调用 wait()不会阻止。使用信号量,如果您调用 release()一次,则下一次对 acquire()的调用将不会阻止。

我希望这会有所帮助。

答案 1 :(得分:0)

首先你必须记住这两件事:

你不能让你的UI线程等待,

您无法从UI线程外部操纵UI

对于那些认为:

的人
  

你自相矛盾......&#34;不要让UI-Thread等待&#34;和   然后你将同步块编码到UI-Thread中?它应该是   好的,如果这两个是唯一访问它,但它是可能的   危险 - &gt;应用没响应

我没有让我的UI等待,仔细查看我的代码虽然注意线程调用wait它释放锁(mSync)。而且在我看来,APR没有机会,你可以在其他答案下面阅读我的评论。

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>

答案 2 :(得分:-1)

对于UI上的任何更改,最佳解决方案是信号量和runOnUiThread!

一个重要的补充:如果你想使用同步 - 请注意同步调用可能会阻塞。即使后台线程只调用synchronized (s) { s.wait(); }在获取锁之后,但在使用wait()再次释放之前,线程可能会因各种原因暂停/暂停。在这种情况下,UI-Thread将阻塞,并且您的UI将挂起,直到再次激活后台线程并获得释放锁定的执行时间。由于Android上的Dalvik-VM将Java-Threads实现为本机linux-threads,因此它们具有不同的优先级并争夺相同的系统资源。由于在重载系统上可能有许多线程的优先级高于后台线程(并发GC,ui-threads,系统线程),后台线程可能会非常慢,因为它获得的资源非常少,而UI-线程将获得高优先级,并经过调整以便经常运行。在最糟糕的情况下,UI-Thread将挂起等待后台线程释放锁定超过5秒并触发“应用程序未响应”

所以解决这个问题的方法是:在一个单独的线程上调用synchronized(s){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();
        }
    }
}