Skip to content

Cannot use threading with Mac OSX Backend #5251

Closed

Description

@nils-werner

The following does not work on Mac OSX:

from __future__ import division

import threading
import time
import sys
import numpy as np
import matplotlib
matplotlib.use('MacOSX')

import matplotlib.pyplot as plt


class async_worker(threading.Thread):
    def __init__(self, fig, axis):
        threading.Thread.__init__(self)
        self.fig = fig
        self.axis = axis

    def run(self):
        i = 0
        while True:
            print "Run thread %d" % i
            sys.stdout.flush()
            i += 1
            time.sleep(0.1)


def main():
    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1)
    ax.plot(np.random.rand(251, 1))

    async_worker(fig, ax).start()
    plt.show()


if __name__ == "__main__":
    main()

I expect to see "Run thread" repeated every 0.1 seconds, no matter if I interact with the plot or not. Instead I only see it repeat when I click and drag around in the MPL figure.

When switching to matplotlib.use('TkAgg'), the second thread runs as expected.

Activity

  1. mdboom commented on Oct 15, 2015

    @mdboom
    Member
  2. mdehoon commented on Oct 16, 2015

    @mdehoon
    Contributor

    I cannot replicate this with the current matplotlib in master; I get the same behavior with the MacOSX backend and the tkagg backend.
    Which version of matplotlib are you using?
    Can you try with the matplotlib release candidate:
    https://github.com/matplotlib/matplotlib/releases/tag/v1.5.0rc2

  3. nils-werner commented on Oct 16, 2015

    @nils-werner
    ContributorAuthor

    I have the same problem with 1.4.3 and after cloning and pip install -eing e7e8bcb: The second thread only runs when I am moving my mouse.

  4. mdehoon commented on Oct 16, 2015

    @mdehoon
    Contributor

    I can confirm the bug if I have interactive : False in matplotlibrc ;
    the bug does not appear if I set interactive : True.
    @nils-werner Can you confirm this?

  5. nils-werner commented on Oct 16, 2015

    @nils-werner
    ContributorAuthor

    When I add plt.interactive(True) to the top of the script

    • The second thread runs
    • Matplotlib does not plot anything, the window seems to freeze (spinning ball of death)
  6. mdehoon commented on Oct 16, 2015

    @mdehoon
    Contributor

    @nils-werner Can you try with the latest master (or release candidate 1.5rc2)?
    If I add plt.interactive(True) to the top of the script, the second thread runs and the window behaves normally.

  7. nils-werner commented on Oct 16, 2015

    @nils-werner
    ContributorAuthor

    I tried it with both stable and master, the behaviour is always the same: The figure window stays blank.

  8. WeatherGod commented on Oct 16, 2015

    @WeatherGod
    Member

    how was python installed/built for you? Maybe it is a subtle difference
    like the whole frameworks/nonframeworks issue?

    On Fri, Oct 16, 2015 at 11:29 AM, Nils Werner [email protected]
    wrote:

    I tried it with both stable and master, the behaviour is always the same:
    The figure window stays blank.

    —
    Reply to this email directly or view it on GitHub
    #5251 (comment)
    .

  9. nils-werner commented on Oct 16, 2015

    @nils-werner
    ContributorAuthor
    brew install python --with-brewed-openssl --framework
    

    and fixed the virtualenv framework issue using https://github.com/gldnspud/virtualenv-pythonw-osx

  10. mdehoon commented on Oct 16, 2015

    @mdehoon
    Contributor

    @nils-werner
    Can you try the following:
    In the function static PyObject* show(PyObject* self) in src/_macosx.m, replace

        [NSApp run];
    

    by

        Py_BEGIN_ALLOW_THREADS
        [NSApp run];
        Py_END_ALLOW_THREADS
    

    That should get you the correct behavior in the non-interactive case.

  11. nils-werner commented on Oct 19, 2015

    @nils-werner
    ContributorAuthor

    Yes, that indeed fixes the issue! Shall I prepare a PR with that fix?

  12. mdehoon commented on Oct 19, 2015

    @mdehoon
    Contributor

    @nils-werner We're not quite there yet; I have not yet been able to reproduce the spinning ball of death in the interactive case. Also there may be a more appropriate place to put the Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS block.

  13. mdehoon commented on Oct 23, 2015

    @mdehoon
    Contributor

    @nils-werner On second thought, since we are in the process of release candidates for the 1.5 release, perhaps it's better to wait with more extensive changes to _macosx.m. So if you could prepare a PR with the simply fix of putting Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS around [NSApp run], then I can accept it.

  14. tacaswell commented on Oct 24, 2015

    @tacaswell
    Member

    @mdehoon Should this be closed or left open for a more through fix?

  15. mdehoon commented on Oct 25, 2015

    @mdehoon
    Contributor

    @tacaswell Let's leave it open. I will have another look after 1.5 final is out.

  16. nils-werner commented on Nov 2, 2015

    @nils-werner
    ContributorAuthor

    I have discovered another issue while trying to draw() from another thread:

    from __future__ import division
    
    import threading
    import time
    import sys
    import numpy as np
    import matplotlib
    matplotlib.use('MacOSX')
    
    import matplotlib.pyplot as plt
    
    
    class async_worker(threading.Thread):
        def __init__(self, fig, axis):
            threading.Thread.__init__(self)
            self.fig = fig
            self.axis = axis
    
        def run(self):
            i = 0
            while True:
                self.fig.canvas.draw()
                print "Run thread %d" % i
                sys.stdout.flush()
                i += 1
                time.sleep(0.1)
    
    
    def main():
        fig = plt.figure()
        ax = fig.add_subplot(1, 1, 1)
        ax.plot(np.random.rand(251, 1))
    
        async_worker(fig, ax).start()
        plt.show()
    
    
    if __name__ == "__main__":
        main()
    

    Whenever I click any of the navigation bar buttons, the entire process deadlocks. Again, when using TkAgg, this works just fine.

  17. mdehoon commented on Nov 7, 2015

    @mdehoon
    Contributor

    You are trying to draw from the secondary thread, while typically you should draw from the main thread only. If you replace self.fig.canvas.draw by self.fig.canvas.invalidate, your script should work. Then the secondary thread merely informs the main thread that it should redraw itself, while the main thread does the actual drawing. I haven't done much testing with threads, so you may still run into some bugs; please let us know if you do.

  18. nils-werner commented on Nov 7, 2015

    @nils-werner
    ContributorAuthor

    When I try to use invalidate I get

    AttributeError: 'FigureCanvasTkAgg' object has no attribute 'invalidate'
    

    I tried in both mpl 1.4.3 and 1.5.0

  19. tacaswell commented on Nov 7, 2015

    @tacaswell
    Member

    What does matplotlib.get_backend() return? invalidate is an OSX backend specific thing, I would not expect it to work with TkAgg.

  20. mdehoon commented on Nov 8, 2015

    @mdehoon
    Contributor

    You could use self.fig.canvas.draw_idle (which calls self.fig.canvas.invalidate on MacOSX backend). The draw_idle function is defined for all backends. In practice, this seems to work for the MacOSX backend only; tkagg gives "RuntimeError: main thread is not in main loop" and gtkagg gives a segmentation fault.

  21. nils-werner commented on Nov 12, 2015

    @nils-werner
    ContributorAuthor

    Ah ok. Well obviously I want to write platform and backend independent code.

    Wasn't draw_idle supposed to be deprecated?

  22. mdehoon commented on Nov 13, 2015

    @mdehoon
    Contributor

    @nils-werner Actually can you provide an example script that shows how you want to use threads in practice? From your current example, it's not clear to me what you are trying to achieve.

  23. mdehoon commented on Nov 25, 2015

    @mdehoon
    Contributor

    Pull request #5562 provides a comprehensive fix for the first issue (October 15).
    For the second issue (November 2), the following script should work:

    import threading
    import time
    import sys
    import numpy as np
    import matplotlib
    matplotlib.use('MacOSX')
    matplotlib.interactive(False)
    
    import matplotlib.pyplot as plt
    
    
    class async_worker(threading.Thread):
        def __init__(self, fig, axis):
            threading.Thread.__init__(self)
            self.fig = fig
            self.axis = axis
    
        def run(self):
            i = 0
            while True:
                self.axis.clear()
                self.axis.plot(np.random.rand(251, 1))
                self.fig.canvas.draw_idle()
                print "Run thread %d" % i
                sys.stdout.flush()
                i += 1
                time.sleep(0.1)
    
    
    def main():
        fig = plt.figure()
        ax = fig.add_subplot(1, 1, 1)
        async_worker(fig, ax).start()
        plt.show()
    
    if __name__ == "__main__":
        main()
  24. jklymak commented on Aug 16, 2018

    @jklymak
    Member

    So far as I can tell the original issue now works in master on my mac, so I'm going to close this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions