|
35 | 35 | import itertools
|
36 | 36 | import logging
|
37 | 37 | from numbers import Integral
|
| 38 | +import threading |
38 | 39 |
|
39 | 40 | import numpy as np
|
40 | 41 |
|
@@ -2365,6 +2366,18 @@ class Figure(FigureBase):
|
2365 | 2366 | *suppressComposite* is a boolean, this will override the renderer.
|
2366 | 2367 | """
|
2367 | 2368 |
|
| 2369 | + # we want to cache the fonts and mathtext at a global level so that when |
| 2370 | + # multiple figures are created we can reuse them. This helps with a bug on |
| 2371 | + # windows where the creation of too many figures leads to too many open |
| 2372 | + # file handles and improves the performance of parsing mathtext. However, |
| 2373 | + # these global caches are not thread safe. The solution here is to let the |
| 2374 | + # Figure acquire a shared lock at the start of the draw, and release it when it |
| 2375 | + # is done. This allows multiple renderers to share the cached fonts and |
| 2376 | + # parsed text, but only one figure can draw at a time and so the font cache |
| 2377 | + # and mathtext cache are used by only one renderer at a time. |
| 2378 | + |
| 2379 | + _render_lock = threading.RLock() |
| 2380 | + |
2368 | 2381 | def __str__(self):
|
2369 | 2382 | return "Figure(%gx%g)" % tuple(self.bbox.size)
|
2370 | 2383 |
|
@@ -3124,33 +3137,33 @@ def clear(self, keep_observers=False):
|
3124 | 3137 | @allow_rasterization
|
3125 | 3138 | def draw(self, renderer):
|
3126 | 3139 | # docstring inherited
|
3127 |
| - |
3128 |
| - # draw the figure bounding box, perhaps none for white figure |
3129 | 3140 | if not self.get_visible():
|
3130 | 3141 | return
|
3131 | 3142 |
|
3132 |
| - artists = self._get_draw_artists(renderer) |
3133 |
| - try: |
3134 |
| - renderer.open_group('figure', gid=self.get_gid()) |
3135 |
| - if self.axes and self.get_layout_engine() is not None: |
3136 |
| - try: |
3137 |
| - self.get_layout_engine().execute(self) |
3138 |
| - except ValueError: |
3139 |
| - pass |
3140 |
| - # ValueError can occur when resizing a window. |
| 3143 | + with self._render_lock: |
3141 | 3144 |
|
3142 |
| - self.patch.draw(renderer) |
3143 |
| - mimage._draw_list_compositing_images( |
3144 |
| - renderer, self, artists, self.suppressComposite) |
| 3145 | + artists = self._get_draw_artists(renderer) |
| 3146 | + try: |
| 3147 | + renderer.open_group('figure', gid=self.get_gid()) |
| 3148 | + if self.axes and self.get_layout_engine() is not None: |
| 3149 | + try: |
| 3150 | + self.get_layout_engine().execute(self) |
| 3151 | + except ValueError: |
| 3152 | + pass |
| 3153 | + # ValueError can occur when resizing a window. |
3145 | 3154 |
|
3146 |
| - for sfig in self.subfigs: |
3147 |
| - sfig.draw(renderer) |
| 3155 | + self.patch.draw(renderer) |
| 3156 | + mimage._draw_list_compositing_images( |
| 3157 | + renderer, self, artists, self.suppressComposite) |
3148 | 3158 |
|
3149 |
| - renderer.close_group('figure') |
3150 |
| - finally: |
3151 |
| - self.stale = False |
| 3159 | + for sfig in self.subfigs: |
| 3160 | + sfig.draw(renderer) |
| 3161 | + |
| 3162 | + renderer.close_group('figure') |
| 3163 | + finally: |
| 3164 | + self.stale = False |
3152 | 3165 |
|
3153 |
| - DrawEvent("draw_event", self.canvas, renderer)._process() |
| 3166 | + DrawEvent("draw_event", self.canvas, renderer)._process() |
3154 | 3167 |
|
3155 | 3168 | def draw_without_rendering(self):
|
3156 | 3169 | """
|
|
0 commit comments