Announcement

Collapse
No announcement yet.

Messagebox in separate thread

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    #21
    This discussion is interesting. I was aware of the issue, and i ignored it.
    Not because i am such a sloppy person, but rather because i tend to look at
    such issues from the engineering point of view: Risk = Probability * Consequence.
    One may strive after absolute certainty, but sometimes all the machinery needed
    to provide that will cause more trouble than the mischief itself, if it happened.

    The probability that a 32 bit variable is misread seems extremely small to me.
    Modern processors handle 32 or 64 bits at the time, which implies that a 32 bit
    variable is put in place in one single operation.
    If it were about a string or an array this would have been different.

    Looking at the possible consequences i do not see big dangers either.
    If this were about software for rocket launching, or the stock exchange, i would
    have been much more careful. But as it is in the given case about a simple
    messagebox, probably the worst that could happen in the unlikely case that
    things would go wrong, is a user saying "Hey didn't work, let me try again".

    In my opinion in the given case it is about a very unlikely phenomenon, that even
    if it happened would not cause any real problems, and i therefore did not find it
    worthwhile to do anything to prevent it.

    Arie Verheul

    Comment


      #22
      Safe global strings using Critical Section

      Code:
      #INCLUDE "win32api.inc"
        
        GLOBAL gCS AS CRITICAL_SECTION
        GLOBAL gstring AS STRING
      FUNCTION PBMAIN () AS LONG
        LOCAL dw AS LONG, hThread AS LONG
        InitializeCriticalSection gCS
        
        Message "Safe global message box"
        ? "WAITING TO EXIT PBMAIN"
        
        DeleteCriticalSection gCS
      END FUNCTION
      SUB Message(s AS STRING)
        LOCAL dw AS DWORD, hThread AS DWORD
        
        entercritical 'keep others out
        gstring = s
        THREAD CREATE Thread1(dw) TO hThread
        SLEEP 50
        THREAD CLOSE hThread TO hThread
        leavecritical 'let others back in
      END SUB
      THREAD FUNCTION Thread1(BYVAL dw AS DWORD) AS DWORD
        ? gstring
      END FUNCTION
      SUB EnterCritical
        EnterCriticalSection gCS
      END SUB
      SUB LeaveCritical
        LeaveCriticalSection gCS
      END SUB

      Comment


        #23
        FWIW, All PowerBASIC intrinsic functions are thread-safe. I have no idea HOW they do it (Critical sections? Thread Local Storage? Thread-indexed tables in the runtime?)

        Of course, I do not care about the HOW, I only care about the WHAT.
        Knowing MCM's outspoken-ness against Global variables, what is the feel for Threaded Variables?

        Seeing how the docs say Threaded is global to the functions in a thread, but local to the thread itself (Or at least thats the way I understand it???)

        I have a situation where 2 threads could be accessing a global that I have started removing the global and make it a function that you set or get a static variable from that function. But maybe using a Threaded variable (Global to my functions in the thread, but local to the thread itself) is the way to go????
        Engineer's Motto: If it aint broke take it apart and fix it

        "If at 1st you don't succeed... call it version 1.0"

        "Half of Programming is coding"....."The other 90% is DEBUGGING"

        "Document my code????" .... "WHYYY??? do you think they call it CODE? "

        Comment


          #24
          THREADED is not the same as a GLOBAL.

          GLOBAL = one address, shared by all threads of execution in which contexts procedures in the current module execute

          THREADED = one address per current thread of execution.... multiple values.
          Michael Mattias
          Tal Systems (retired)
          Port Washington WI USA
          [email protected]
          http://www.talsystems.com

          Comment


            #25
            I think you're safe in handling a global LONG or DWORD value. That's almost guaranteed to take just one instruction, if you're doing something simple like a LET A=B sort of thing. BUT.

            That's a big BUT.

            Thread-safe programming can be monstrously complicated to get exactly perfect, perhaps because we're generally trained to think of things working in sequential order, and threads are asynchronous. And there's the serious problem that a thread error may only appear "at random", when the moon is full and two people accidentally read their in-boxes at the same microsecond.

            I'd like to say, "don't". But there's no point in avoiding it, the ability to spawn a new thread is unbelievably powerful. Just try not to cut off all your fingers with that knife. Read up on thread programming, and be careful out there.

            Comment


              #26
              If this were about software for rocket launching, or the stock exchange, i would
              have been much more careful. But as it is in the given case about a simple
              messagebox
              That's one way to look at it.

              But as Mr. Hanlin states, multithreaded programming can be "monstrously complex." The chance to learn the proper techniques in a *simple* application like this demo is a blessing.

              Or perhaps you would prefer to learn it with a factory production line down whilst your client's attorney is multiplying total downtime by $5000 per hour?

              MCM
              Michael Mattias
              Tal Systems (retired)
              Port Washington WI USA
              [email protected]
              http://www.talsystems.com

              Comment


                #27
                Originally posted by Tom Hanlin View Post
                I think you're safe in handling a global LONG or DWORD value. That's almost guaranteed to take just one instruction, if you're doing something simple like a LET A=B sort of thing. BUT.
                Only true if the long is correctly aligned thus my restriction that it is always true if only used as a True/False flag. There are of course many very fast Interlocked calls that can be used for common situations

                That's a big BUT.

                Thread-safe programming can be monstrously complicated to get exactly perfect, perhaps because we're generally trained to think of things working in sequential order, and threads are asynchronous. And there's the serious problem that a thread error may only appear "at random", when the moon is full and two people accidentally read their in-boxes at the same microsecond.

                I'd like to say, "don't". But there's no point in avoiding it, the ability to spawn a new thread is unbelievably powerful. Just try not to cut off all your fingers with that knife. Read up on thread programming, and be careful out there.
                Yes it takes some reading but in PB is well supported and apart from multiple threads transferring data between each other are relatively simple.
                Unfortunately the most often quoted example in source code ties itself in knots because it is unneccasarily complicated so the author then uses more complicated code to fix the problem he created, I quote from the program
                Code:
                 ' I had to use a string ptr here instead of CONTROL GET TEXT because the calling thread was in a wait
                 ' state (WaitForSingleObject) and apparently (undocumented but not reasonably) CONTROL GET TEXT does
                 ' "something" which must execute in the context of the same thread (suspended) as the dialog.
                 ' Apparently (that means also not documented), DIALOG GET USER does not need to execute anything in the
                 ' context of the thread in which the dialog was created.
                Nearly all the complicated code goes away if the calling main thread is not itself suspended with a totally unneed WFSO immediately after the thread creation. In fact the program then uses a timer to update the screen which really defeats the purpose of a worker thread.
                Now he claims WFSO takes zero time, I assume he is a believer in string theory and his programs work in some higher dimension, hopefully he will soon demonstrate a program that finishes before it starts

                Comment


                  #28
                  ...we...got a little off the subject... The OP asked about MessageBoxes [and] an answer was given...
                  I agree. I posted a comment totally in passing and here we are.

                  Not that anybody will listen but I think it's time this thread died a quiet, (un)dignified death.
                  There are no atheists in a fox hole or the morning of a math test.
                  If my flag offends you, I'll help you pack.

                  Comment


                    #29
                    I had to spend most of this sunday on tidying up the garden for the winter, and usually that helps.
                    At least i think it did.

                    In my example the thread finishes immediately after a button on the messagebox is clicked.
                    This means that if i test with the Thread Status function if the thread has finished,
                    and find that the thread has finished indeed, i can after that always read safely any variable.

                    I suspect this is more or less equivalent to the WaitForSingleObject approach, with the difference
                    that it is not tested all the time, but only when i need to know this.
                    Would this be an acceptable approach for this simple case?

                    In the example below i implemented this change. For the convenience of PBWin users i eliminated the console.

                    Arie Verheul



                    Code:
                     
                    #Console Off                    ' Remove for PBWIN
                    #Include "win32api.inc"
                     
                    Function PBMain () As Long
                     
                        Dim MsgText  As Global Asciiz*60
                        Dim Response As Global Long
                     
                        Local hThread       As Dword
                        Local ThreadVar     As Dword
                        Local hWnd          As Dword
                        Local FontHndl      As Dword
                        Local I,J,K,L       As Long
                        Local ThreadStatus  As Long
                        Local ThreadStarted As Long
                        
                        '-------------------------------------------------------------
                        ' Set up graphic window
                     
                        Graphic Window "Messagebox in thread", 100,100,300,200 To hWnd
                        Graphic Attach hWnd,0
                        Graphic Color %White,%Black
                        Graphic Clear
                        
                        Font New "Courier New",12 To FontHndl
                        Graphic Set Font FontHndl
                        '-------------------------------------------------------------
                        
                        MsgText       = "Just say so if you want to stop when this set is finished"
                        
                        ThreadStarted = 0
                     
                        Do
                            For J = 2 To 9
                                For I = 1 To 10
                                    K = I * J
                                    Graphic Print Using$( Space$(8) + "## x # = ##",I,J,K)
                                    Sleep 100
                                Next
                                Sleep 1000
                                Graphic Clear
                                If IsWindow(hWnd) = 0 Then Exit Function    ' End program if window was closed
                            Next
                     
                            Incr L
                            
                            '----------------------------------------------------------------------------------
                            If L = 1 Then                                   ' Show message box
                                
                                Thread Create Message (ThreadVar) To hThread
                                Threadstarted = -1                          ' Set flag that messagebox thread was started
                            End If
                            
                            '----------------------------------------------------------------------------------
                    	' This is to test if thread is still running or has finished
                     
                            If ThreadStarted Then                           ' Test if thread has finished
                                Thread Status hThread To ThreadStatus
                                If ThreadStatus = 259 Then Iterate Loop     ' 259 corresponds to running thread
                            End If
                            '----------------------------------------------------------------------------------
                     
                        Loop Until Response = 6
                     
                    End Function
                     
                    '------------------------------------------------------------------------------------------
                    ' Message box thread function
                     
                    Thread Function Message (ByVal ThreadVar As Dword)  ' ThreadVar is a dummy variable
                     
                        MessageBox 0, MsgText, "Test Message Box" , 4 To Response
                     
                    End Function
                    '------------------------------------------------------------------------------------------

                    Comment


                      #30
                      I suspect this is more or less equivalent to the WaitForSingleObject approach, with the difference
                      that it is not tested all the time, but only when i need to know this.
                      Not the same at all. WFSO does not return until the event (thread handle) is signalled or the timeout period is reached. THREAD STATUS gets the exit code of the thread and returns.

                      WFSO could never work as a 'drop in replacement' for a THREAD STATUS.....IF.. EXIT ...END IF test... you'd never get to the 'END IF'

                      "As a general rule" you would never call WFSO from your primary GUI thread while you still have a screen up.

                      MCM
                      Michael Mattias
                      Tal Systems (retired)
                      Port Washington WI USA
                      [email protected]
                      http://www.talsystems.com

                      Comment


                        #31
                        Originally posted by Michael Mattias View Post
                        "As a general rule" you would never call WFSO from your primary GUI thread while you still have a screen up.
                        MCM
                        Totally agree, perhaps you should do an update to your much quoted 2008 demo

                        Comment


                          #32
                          Thanks all for your comment so far. Many different views were brought forward,
                          but it would be great if this thread could end with a conclusive result.

                          While reading on the matter i got the impression that only the technique using the
                          WinApi CriticalSection functions enjoys broad support. See for example Kev Peel's post at:


                          But as was brought forward both by me and by Edwin Knoppert, this seems far too complex for
                          this simple purpose. There must be a simpler way, and if it didn't exist it should be invented.

                          It seems important to obtain clarity about the issue how a 32 bit variable is moved to memory.
                          In my ignorance i supposed that there is only one possible way: all 32 bits at the time.
                          In that case there would never be a synchronisation issue with a 32 bit variable.
                          Tom Hanlin supports this view, but has general doubts.
                          Edwin Knoppert and John Petty however suppose that this is only true if the variable is properly aligned.

                          Does this really mean that the processor might move a part of a 32 bit variable to memory, go away to do
                          other things, and come back later for the remainder of the variable? Seems really weird to me.
                          However if this can be solved by aligning the variable, no problem, how is that done?

                          Or conversely, how small should a variable be to make sure that it is transferred in one single operation ?

                          Arie Verheul

                          Comment


                            #33
                            Showed polled, non-polled, crititical section, now this.
                            I thought you just wanted to display a message box in another thread?
                            Code:
                            GLOBAL gs AS STRING
                            FUNCTION PBMAIN&
                              MB "one"
                              MB "two
                              SLEEP 2000
                            END FUNCTION
                            SUB MB(s AS STRING)
                              gs=s
                              THREAD CREATE MessageBoxThread(x???) TO hThread???
                              SLEEP 50
                              THREAD CLOSE hThread??? TO hThread???
                            END SUB
                            THREAD FUNCTION MessageBoxThread(BYVAL y AS DWORD) AS DWORD
                              ? gs
                            END FUNCTION
                            Last edited by Mike Doty; 19 Oct 2009, 03:04 PM. Reason: VARPTR wasn't needed

                            Comment


                              #34
                              Or conversely, how small should a variable be to make sure that it is transferred in one single operation ?
                              If you mean "atomically" by the CPU, you can't. To ensure 'no thread switch during operation' you need to use one of the Interlockedxxxx functions or a synchronization object.

                              Note that a PB statement calling the Interlocked functions such as...
                              Code:
                                 InterlockedExchange     g_intvar,   g_intVar + 10
                              .. is not atomic, as multiple instructions are required to calculate the value of the expression 'g_intvar + 10'

                              MCM
                              Michael Mattias
                              Tal Systems (retired)
                              Port Washington WI USA
                              [email protected]
                              http://www.talsystems.com

                              Comment


                                #35
                                >Totally agree, perhaps you should do an update to your much quoted 2008 demo

                                You know, I thought about qualifying that "general rule" statement at the time, because I know I do this often. But I said, "nah, it's not necessary."

                                For me it is one consistent exception... I always do this in real life . although not necessarily in demo life.

                                MCM
                                Michael Mattias
                                Tal Systems (retired)
                                Port Washington WI USA
                                [email protected]
                                http://www.talsystems.com

                                Comment


                                  #36
                                  >However if this can be solved by aligning the variable, no problem, how is that done?
                                  I believe that in the newer PB versions, global variables are aligned already.
                                  You have to verify this.

                                  If not you can create a global string of 7 bytes and calculate the correct offset.
                                  Global s as String * 7
                                  Global p as Dword ptr
                                  p = varptr( s )
                                  what is the address? / 4... and so on.
                                  (you'll figure out a fine divide or mod function )
                                  hellobasic

                                  Comment


                                    #37
                                    Arie,
                                    all PB declared 32 bit variables are aligned. All smaller variables are also considered aligned, although not on 32-bit boundaries they still exisist entirely within a single 4-byte aligned block of memory.
                                    You can force them to be unaligned, e.g. by accessing them with pointers and reading/writing them from/to odd addresses but the default condition is to have them aligned.

                                    The CPU will access all fully aligned data indivisibly. Be careful with 64 bit data (QUADs, DOUBLEs) and EXTs the compiler does not fully align them automatically. Quads need to be aligned on 8 byte boundaries and are only 4-byte aligned by the compiler.


                                    For synchronisation purposes, it doesn't matter whether a variable is aligned or not. What matters is that the access of that variable needs to be indivisible.
                                    With certain ASM instructions you know the instruction is indivisible but it's more difficult to determine that with a compiled instruction.
                                    Tom's example of A=B, when compiled actually takes 2 opcodes so it is not indivisible unless one (not both!) of the variables are register variables.

                                    The XCGH opcode which will exchange a register and a memory variable is always indivisible as are a few system instructions.
                                    Using ASM you can specify certain other instructions to be indivisible by using the LOCK prefix.

                                    It was so much easier in the good old days when you simply used !CLI to stop interrupts until you were finished.

                                    Paul.

                                    Comment


                                      #38
                                      No global

                                      Code:
                                      FUNCTION PBMAIN&
                                        LOCAL z AS ASCIIZ * 128
                                        MB "No global"
                                        SLEEP 3000
                                      END FUNCTION
                                      SUB MB(z AS ASCIIZ)
                                        LOCAL x AS DWORD, hThread AS DWORD
                                        x = VARPTR(z)
                                        THREAD CREATE MessageBoxThread(x) TO hThread
                                        SLEEP 50
                                        THREAD CLOSE hThread TO hThread
                                      END SUB
                                      THREAD FUNCTION MessageBoxThread(BYVAL y AS DWORD) AS DWORD
                                        LOCAL z  AS ASCIIZ * 128
                                        ? PEEK$(y,128)
                                      END FUNCTION
                                      Last edited by Mike Doty; 19 Oct 2009, 04:08 PM. Reason: Placed "No global" in the call to eliminate another line

                                      Comment


                                        #39
                                        This one supports style and title

                                        Code:
                                        TYPE MyUDT
                                          txt      AS STRING * 128
                                          MyStyle  AS INTEGER
                                          title    AS STRING * 128
                                        END TYPE
                                         
                                        FUNCTION PBMAIN&
                                          LOCAL z AS ASCIIZ * 128
                                          LOCAL x AS LONG
                                          mb "How now brown cow", %MB_ICONERROR, "Title"
                                          ? "Click to exit"
                                        END FUNCTION
                                         
                                        SUB MB(txt AS STRING, STYLE AS INTEGER, Title AS STRING)
                                          LOCAL x AS MyUDT, hThread AS DWORD
                                          x.txt = txt
                                          x.MyStyle = STYLE
                                          x.Title = Title
                                          THREAD CREATE MessageBoxThread(VARPTR(x)) TO hThread???
                                          SLEEP 50
                                          THREAD CLOSE hThread??? TO hThread???
                                        END SUB
                                         
                                        THREAD FUNCTION MessageBoxThread(BYVAL y AS DWORD) AS DWORD
                                          LOCAL x  AS MyUDT POINTER
                                          x = y
                                          LOCAL txt   AS STRING
                                          LOCAL MyStyle AS INTEGER
                                          LOCAL Title AS STRING
                                          
                                          txt       = @x.txt
                                          MyStyle   = @x.MyStyle
                                          title     = @x.title
                                          ? txt,MyStyle,title
                                        END FUNCTION

                                        Comment


                                          #40
                                          Sleep may not help you here.
                                          When a computer is severely in use it may not even execute the thread before the end of sleep call.
                                          To solve that i ever did a similar thing, i passed allocated memory instead, the thread deletes the memory.

                                          You see, using threads is not that simple.
                                          You must guarantee for several cpu types and scenario's like memory hog and such.
                                          hellobasic

                                          Comment

                                          Working...
                                          X
                                          😀
                                          🥰
                                          🤢
                                          😎
                                          😡
                                          👍
                                          👎