• WorkingWithThreads


    Introduction

    We'll need a threading recipe, this is the old Mandelbrot frame a number of us were working on way back when. Not really heavily threaded, but only thing I have that uses normal threads (not micro-threads).

    See also LongRunningTasksMainLoopAsThread.

     

    Example

     

    切换行号显示
    #! /usr/bin/env python
    #############################################################################
    
    """
    Mandelbrot program, for no particular reason.
    
    John Farrell: initial version
    Robin Dunn: introduced wxImage.SetData instead of wxBitmapFromData
    Ionel Simionescu: used the Numeric package, and rewrote all of the
                      computation and data displaying code
    Alexander Smishlajev: suggestions on optimising loops and drawing
    Markus Gritsch: in-place calculation in the mandelbrot while loop
    Mike Fletcher: minor changes
    
    [06/24/09] Cody Precord: Cleanup, get it working with wxPython2.8,
                             fixes for thread safety,
                             and fixes for old deprecated/obsolete code.
    
    """
    
    import wx
    import threading
    # TODO update to use numpy instead of old Numeric package
    import numpy.oldnumeric as Numeric
    
    class MandelbrotGenerator:
            """Slightly slower mandelbrot generator, this one uses instance
            attributes instead of globals and provides for "tiling" display
    
            """
            def __init__(self, width=200, height=200,
                         coordinates=((-2.0,-1.5), (1.0,1.5)),
                         xdivisor=0, ydivisor=0,
                         iterations=255,
                         redrawCallback=None,
                         statusCallback=None):
    
                self.width = width
                self.height = height
                self.coordinates = coordinates
    
                ### should check for remainders somewhere and be a little
                # smarter about valid ranges for divisors (100s of divisions
                # in both directions means 10000s of calcs)...
                if not xdivisor:
                    xdivisor = 1 #width/50 or 1
                if not ydivisor:
                    ydivisor = 10 #height/50 or 1
                self.xdivisor = xdivisor
                self.ydivisor = ydivisor
                self.redrawCallback = redrawCallback
                self.statusCallback = statusCallback or self.printStatus
                self.MAX_ITERATIONS = iterations
                self.data = Numeric.zeros(width*height)
                self.data.shape = (width, height)
    
                ### Set up tiling info, should really just do all the setup here and use a
                ### random choice to decide which tile to compute next
                self.currentTile = (-1,0)
                self.tileDimensions = ( width/xdivisor, height/ydivisor )
                self.tileSize = (width/xdivisor)* (height/ydivisor)
                (xa,ya), (xb,yb) = coordinates
                self.x_starts = Numeric.arange( xa, xb+((xb-xa)/xdivisor), (xb-xa)/xdivisor)
                self.y_starts = Numeric.arange( ya, yb+((yb-ya)/ydivisor), (yb-ya)/ydivisor)
    
            def DoAllTiles( self ):
                while self.DoTile():
                    pass
    
            def DoTile(self, event=None):
                """Triggered event to draw a single tile into the data object"""
                x_index, y_index = self.currentTile
                if x_index < self.xdivisor - 1:
                    self.currentTile = x_index, y_index = x_index+1, y_index
                elif y_index < self.ydivisor-1:
                    self.currentTile = x_index, y_index = 0, y_index+1
                else:
                    if self.redrawCallback is not None:
                        self.redrawCallback(self.data, False)
                    return False
    
                print 'starting iteration', x_index, y_index
                coords = ((self.x_starts[x_index],self.y_starts[y_index]),
                          (self.x_starts[x_index+1],self.y_starts[y_index+1]),)
                part = self._tile( coords )
                part.shape = self.tileDimensions[1], self.tileDimensions[0]
    
                xdiv = self.width / self.xdivisor
                ydiv = self.height / self.ydivisor
                from_idx = ydiv * y_index
                self.data[from_idx : ydiv * (y_index+1), xdiv * x_index: xdiv * (x_index+1), ] = part
                if self.redrawCallback:
                    self.redrawCallback(self.data, True) # there may be more to do...
                return True
    
            def printStatus(self, *arguments ):
                pass #print arguments
    
            def _tile(self, coordinates):
                """Calculate a single tile's value"""
                (c, z) = self._setup(coordinates)
                iterations = 0
                size = self.tileSize
                i_no = Numeric.arange(size)       # non-overflow indices
                data = self.MAX_ITERATIONS + Numeric.zeros(size)
                # initialize the "todo" arrays;
                # they will contain just the spots where we still need to iterate
                c_ = Numeric.array(c).astype(Numeric.Complex32)
                z_ = Numeric.array(z).astype(Numeric.Complex32)
                progressMonitor = self.statusCallback
    
                while (iterations < self.MAX_ITERATIONS) and len(i_no):
                    # do the calculations in-place
                    Numeric.multiply(z_, z_, z_)
                    Numeric.add(z_, c_, z_)
                    overflow = Numeric.greater_equal(abs(z_), 2.0)
                    not_overflow = Numeric.logical_not(overflow)
                    # get the indices where overflow occured
                    ####overflowIndices = Numeric.compress(overflow, i_no) # slower
                    overflowIndices = Numeric.repeat(i_no, overflow) # faster
    
                    # set the pixel indices there
                    for idx in overflowIndices:
                        data[idx] = iterations
    
                    # compute the new array of non-overflow indices
                    i_no = Numeric.repeat(i_no, not_overflow)
    
                    # update the todo arrays
                    c_ = Numeric.repeat(c_, not_overflow)
                    z_ = Numeric.repeat(z_, not_overflow)
                    iterations = iterations + 1
                    progressMonitor(iterations, 100.0 * len(i_no) / size)
                return data
    
            def _setup(self, coordinates):
                """setup for processing of a single tile"""
                # we use a single array for the real values corresponding to the x coordinates
                width, height = self.tileDimensions
                diff = coordinates[1][0] - coordinates[0][0]
                xs = 0j + (coordinates[0][0] + Numeric.arange(width).astype(Numeric.Float32) * diff / width)
    
                # we use a single array for the imaginary values corresponding to the y coordinates
                diff = coordinates[1][1] - coordinates[0][1]
                ys = 1j * (coordinates[0][1] + Numeric.arange(height).astype(Numeric.Float32) * diff / height)
    
                # we build <c> in direct correpondence with the pixels in the image
                c = Numeric.add.outer(ys, xs)
                z = Numeric.zeros((height, width)).astype(Numeric.Complex32)
    
                # use flattened representations for easier handling of array elements
                c = Numeric.ravel(c)
                z = Numeric.ravel(z)
                return (c, z)
    
    #### GUI ####
    class MandelCanvas(wx.Window):
        def __init__(self, parent, id=wx.ID_ANY,
                     width=600, height=600,
                     coordinates=((-2.0,-1.5),(1.0,1.5)),
                     weights=(16,1,32), iterations=255,
                     xdivisor=0, ydivisor=0):
            wx.Window.__init__(self, parent, id)
    
            # Attributes
            self.width  = width
            self.height = height
            self.coordinates = coordinates
            self.weights = weights
            self.parent = parent
            self.border = (1, 1)
            self.bitmap = None
            self.colours = Numeric.zeros((iterations + 1, 3))
            arangeMax = Numeric.arange(0, iterations + 1)
            self.colours[:,0] = Numeric.clip(arangeMax * weights[0], 0, iterations)
            self.colours[:,1] = Numeric.clip(arangeMax * weights[1], 0, iterations)
            self.colours[:,2] = Numeric.clip(arangeMax * weights[2], 0, iterations)
    
            self.image = wx.EmptyImage(width, height)
            self.bitmap = self.image.ConvertToBitmap()
            self.generator = MandelbrotGenerator(width=width, height=height,
                                                 coordinates=coordinates,
                                                 redrawCallback=self.dataUpdate,
                                                 iterations=iterations,
                                                 xdivisor=xdivisor,
                                                 ydivisor=ydivisor)
    
            # Setup
            self.SetSize(wx.Size(width, height))
            self.SetBackgroundColour(wx.NamedColour("black"))
            self.Bind(wx.EVT_PAINT, self.OnPaint)
    
            # Start generating the image
            self.thread = threading.Thread(target=self.generator.DoAllTiles)
            self.thread.start()
    
        def dataUpdate(self, data, more=False):
            if more:
                data.shape = (self.height, self.width)
                # build the pixel values
                pixels = Numeric.take(self.colours, data)
                # create the image data
                bitmap = pixels.astype(Numeric.UnsignedInt8).tostring()
    
                # create the image itself
                def updateGui():
                    """Need to do gui operations back on main thread"""
                    self.image.SetData(bitmap)
                    self.bitmap = self.image.ConvertToBitmap()
                    self.Refresh()
                wx.CallAfter(updateGui)
    
        def OnPaint(self, event):
            dc = wx.PaintDC(self)
            dc.BeginDrawing()
            if self.bitmap != None and self.bitmap.IsOk():
                dc.DrawBitmap(self.bitmap, 0, 0, False)
            dc.EndDrawing()
    
    class MyFrame(wx.Frame):
        def __init__(self, parent, ID, title):
            wx.Frame.__init__(self, parent, ID, title)
    
            self.CreateStatusBar()
            self.Centre(wx.BOTH)
            mdb = MandelCanvas(self, width=400, height=400, iterations=255)
    
            # Layout
            sizer = wx.BoxSizer(wx.VERTICAL)
            sizer.Add(mdb, 0, wx.EXPAND)
            self.SetAutoLayout(True)
            self.SetInitialSize()
    
    class MyApp(wx.App):
        def OnInit(self):
            frame = MyFrame(None, wx.ID_ANY, "Mandelbrot")
            frame.Show(True)
            self.SetTopWindow(frame)
            return True
    
    if __name__ == '__main__':
        app = MyApp(0)
        app.MainLoop()
    

     

    Display

     


    Here's a different approach to using a thread for long running calculations. This demo was modified to help show how, where and when the calculation thread sends its various messages back to the main GUI program. The original app is called wxPython and Threads posted by Mike Driscoll on the Mouse vs Python blog.

    The "calculations" are simulated by calling time.sleep() for a random interval. Using sleep() is safe because the thread function isn't part of the wx GUI. The thread code uses function Publisher() from wx.lib.pubsub to send data messages back to the main program. When the main program receives a message from the thread it does a little bit of data decoding to determine which of four kinds of message it is. The GUI then displays the data appropriate for its decoded message type. Receiving messages is handled as events so the main program's MainLoop() can go on doing whatever else it needs to do. However, this demo is so simple that there happens to be nothing else to do in the meantime.

     

    切换行号显示
       1     def DisplayThreadMessages( self, msg ) :
       2         """ Receives data from thread and updates the display. """
       3 
       4         msgData = msg.data
       5         if isinstance( msgData, str ) :
       6             self.displayTxtCtrl.WriteText( 'Textual message = [ %s ]
    ' % (msgData) )
       7 
       8         elif isinstance( msgData, float ) :
       9             self.displayTxtCtrl.WriteText( '
    ' )   # A blank line separator
      10             self.displayTxtCtrl.WriteText( 'Processing time was [ %s ] secs.
    ' % (msgData) )
      11 
      12         elif isinstance( msgData, int ) :
      13 
      14             if (msgData == -1) :    # This flag value indicates 'Thread processing has completed'.
      15                 self.btn.Enable()    # The GUI is now ready for another thread to start.
      16                 self.displayTxtCtrl.WriteText( 'Integer ThreadCompletedFlag = [ %d ]
    ' % (msgData) )
      17 
      18             else :
      19                 self.displayTxtCtrl.WriteText( 'Integer Calculation Result = [ %d ]
    ' % (msgData) )
      20         #end if
      21 
      22     #end def
    

    TypicalRun.png

    A much more flexible message encoding/decoding scheme can easily be substituted with just a little more ingenuity. See LongRunningTasks for a much more in-depth look at threading and also two-way communication. - Ray Pasco

     

    Display

  • 相关阅读:
    黄聪:C#中CefSharp的简单使用
    ArcGIS Pro关闭窗口和退出
    AddOverlay
    ArcGIS Pro固定纸张限定比例尺自动调整纵横打印
    ArcGIS pro增加一个独立表到地图
    ArcGIS Pro How to remove standalone table from contents
    ArcGIS Pro的进度条
    CreatePolygonGraphicElement
    Creating a Group with a List of Elements
    ArcGISPro理解多线程
  • 原文地址:https://www.cnblogs.com/dengyigod/p/3304577.html
Copyright © 2020-2023  润新知