none
VB Multithreading - Accessing a function simultaneously with different threads RRS feed

  • Question

  • Hey,

    I am writing a program where many of my functions serve a modular/reusable purpose.

    I designate a new Class (of same type) to every new thread, with their very own properties that they each access (inside the class).

    Sometimes, when two functions want to access a modular function, the other thread waits for the first thread to finish,

    which is very bothersome because I end up having only one of them work effectively at a time.

    The modular functions / sub are not shared and include only ByVal and Optional ByVal Parameters.

    They usually access User32 DLL functions with <Out>/ByRef Parameters.

    I'd obviously want to access the "Threadhandling" classes properties, so shared functions are a no go.

    My goal is to remove the wait and have each thread access a seperate instance of the function.

    Since I wasn't able to find anything relevant on the matter I hope someone here could shed his wisdom :)

    I am using VS 2017 CE with .Net 4.6.1

    Monday, April 2, 2018 10:25 PM

Answers

  • When the Threads created in x and y both are at a spot where they want to "click" one waits for the other.

    I'd very much appreciate you pointing out my flaw, as I've been having quite a lot of trouble trying to make it work properly.

    Try using PostMessage instead of SendMessage.  SendMessage blocks If I remember correctly.

        Public Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Int32, ByVal wMsg As Int32, ByVal wParam As Int32, ByVal lParam As Int32) As IntPtr
    


    "Those who use Application.DoEvents() have no idea what it does and those who know what it does never use it."

    - from former MSDN User JohnWein

    SerialPort Info

    Multics - An OS ahead of its time.

    • Marked as answer by VonRiva Friday, April 13, 2018 6:16 PM
    Tuesday, April 3, 2018 9:03 PM

All replies

  • Sometimes, when two functions want to access a modular function, the other thread waits for the first thread to finish, which is very bothersome because I end up having only one of them work effectively at a time.

    All members of a module are shared.  If you want your code to execute separate instances of that function then put the function code in a class and create a class instance in the thread.

    • Proposed as answer by Mr. Monkeyboy Tuesday, April 3, 2018 2:25 AM
    Monday, April 2, 2018 11:08 PM
  • There is much that you are saying that I don't understand so I apologize if my comments are not relevant.

    Functions can be thread-safe and when they are they can be used by multiple threads simultaneously. Do you understand thread safety? To get thread safety you need to ensure that everything you call is thread-safe and the MSDN documentation is good about documenting that.

    I have used C++ and C# but not VB much. My understanding is that shared in VB.Net is generally equivalent to static in C# and C++. There is a difference between shared methods and shared variables. Yes, a shared variable that is modified in the method would make a method not thread-safe. I can't think of a reason why a static method can't be thread safe if everything it does is.

    The concept of thread-safety is often called something else for other operating systems; for old-style IBM mainframe computers the common term is "reentrant" because code that is reentrant can be used by multiple threads or tasks simultaneously. Reentrancy is not the same thing as recursion but recursion would generally require reentrancy/thread safety.



    Sam Hobbs
    SimpleSamples.Info

    • Proposed as answer by Mr. Monkeyboy Tuesday, April 3, 2018 2:25 AM
    Tuesday, April 3, 2018 1:46 AM
  • Sometimes, when two functions want to access a modular function, the other thread waits for the first thread to finish,

    which is very bothersome because I end up having only one of them work effectively at a time.

    The modular functions / sub are not shared and include only ByVal and Optional ByVal Parameters.

    I am using VS 2017 CE with .Net 4.6.1

    This is not CE but shows a Function being accessed by multiple threads.

        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim t As Task = Task.Run(Sub() DoTest())
        End Sub
    
        Private Sub DoTest()
            Dim ts() As String = {"when", "two", "functions", "want", "to", "access"}
            Dim alltasks As New List(Of Task)
            Dim stpw As Stopwatch = Stopwatch.StartNew
            For Each ats As String In ts
                Dim t As Task
                t = New Task(Sub(word)
                                 Dim rs As String = RevTheString(word.ToString)
                                 Me.Invoke(Sub()
                                               TextBox1.Text = rs
                                               'refresh and sleep so results are visible
                                               TextBox1.Refresh()
                                               Threading.Thread.Sleep(250)
                                           End Sub)
                             End Sub, ats) 'pass ats to task as object
                alltasks.Add(t)
                t.Start()
            Next
    
            Debug.WriteLine((From at In alltasks Where at.Status = TaskStatus.Running Select at).Count)
            Task.WaitAll(alltasks.ToArray)
            stpw.Stop()
            Debug.WriteLine(stpw.Elapsed) 'with .Sleep(250) should be about 1.5 secs.
            Debug.WriteLine((From at In alltasks Where at.Status = TaskStatus.RanToCompletion Select at).Count)
        End Sub
    
        Private Function RevTheString(someString As String) As String
            Dim rv As String = someString.ToCharArray.Reverse.ToArray
            Return rv
        End Function
    I am not sure how CE is different from this Task model.  Good luck.


    "Those who use Application.DoEvents() have no idea what it does and those who know what it does never use it."

    - from former MSDN User JohnWein

    SerialPort Info

    Multics - An OS ahead of its time.


    • Edited by dbasnett Tuesday, April 3, 2018 1:25 PM
    Tuesday, April 3, 2018 1:24 PM
  • The functions were already each created in a seperate class instance.

    I also tried capsuling the single function where I noticed this behavior in a new class as you suggested to no avail.

    Tuesday, April 3, 2018 8:11 PM
  • If I understand it correctly then making a function thread-save basically means removing all variables that may be shared between both threads and instead putting them as paramaters?

    My Code looks something like this dummy I made for demonstration, but it seemingly isn't Thread save.

    When the Threads created in x and y both are at a spot where they want to "click" one waits for the other.

    I'd very much appreciate you pointing out my flaw, as I've been having quite a lot of trouble trying to make it work properly.

    Class Initialize Sub Main Dim x As New ThreadHandler x.Main()

    Dim y As New ThreadHandler
    y.Main()

    End Sub End Class Class ThreadHandler Dim Number As Byte = 0 Public Property objSendInput As SendInput With {.Process = something} Public Property processResolution As Resolution 'Resolution is a byte enumeration Public Property RunningThread As New Thread(AddressOf ThreadStart) With {.IsBackground = True, .Name = "runningThread" & Number} Sub Main() runningThread.Start() End Sub Sub ThreadStart() 'Do stuff Click() 'Do Stuff End Sub Private Sub Click() If processResolution = Resolution.Half Then objSendInput.Click(250, 250, False) Else objSendInput.Click(500, 500) End If End Sub End Class Class SendInput Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Int32, ByVal wMsg As Int32, ByVal wParam As Int32, ByVal lParam As Int32) As IntPtr Public Declare Function GetWindowRect Lib "user32.dll" (ByVal hwnd As Int32, ByRef lpRect As Rectangle) As Boolean Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Int32 Public Property RunningProcess As Process Sub Click(ByVal x As Short, ByVal y As Short) Dim MousePos As Long Dim hWind As Integer Dim rect As Rectangle If RunningProcess Is Nothing Then hWind = FindWindow(Nothing, "NotePad") Else hWind = RunningProcess.MainWindowHandle End If GetWindowRect(hWind, rect) x = rect.Width - rect.X y = rect.Height - rect.Y MousePos = MakeDWord(x, y) SendMessage(hWind, WM_LBUTTONDOWN, 0, MousePos) SendMessage(hWind, WM_LBUTTONUP, 0, MousePos) End Sub End Class


    Tuesday, April 3, 2018 8:29 PM
  • When the Threads created in x and y both are at a spot where they want to "click" one waits for the other.

    I'd very much appreciate you pointing out my flaw, as I've been having quite a lot of trouble trying to make it work properly.

    Try using PostMessage instead of SendMessage.  SendMessage blocks If I remember correctly.

        Public Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Int32, ByVal wMsg As Int32, ByVal wParam As Int32, ByVal lParam As Int32) As IntPtr
    


    "Those who use Application.DoEvents() have no idea what it does and those who know what it does never use it."

    - from former MSDN User JohnWein

    SerialPort Info

    Multics - An OS ahead of its time.

    • Marked as answer by VonRiva Friday, April 13, 2018 6:16 PM
    Tuesday, April 3, 2018 9:03 PM

  • Try using PostMessage instead of SendMessage.  SendMessage blocks If I remember correctly.

        Public Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Int32, ByVal wMsg As Int32, ByVal wParam As Int32, ByVal lParam As Int32) As IntPtr

    Didn't work out unfortunately, this only shortened the time the Click function needs.

    • Edited by VonRiva Tuesday, April 3, 2018 9:19 PM
    Tuesday, April 3, 2018 9:17 PM
  • My Code looks something like this dummy I made for demonstration, but it seemingly isn't Thread save.

    When the Threads created in x and y both are at a spot where they want to "click" one waits for the other.

    That conclusion is inconsistent.  If one thread blocks until another completes a task, then that task is inherently 'thread safe'. But that doesn't mean the task won't also be thread-safe without blocking - the blocking might have nothing to do with thread safety. The idea of thread safe does not usually extend to ensuring the logical flow of procedures: it only refers to the assurance that overlapped calls won't corrupt data or crash the system

    If you need to ensure specific sequencing in your message calls, which seems likely in this case, then use a blocking call such as SendMessage to force that sequence. The wait that you refer to is necessary to ensure that sequence.  If the application logic does not require that, then use a non-blocking form such as PostMessage and accept that things might not occur in the order expected.

    Tuesday, April 3, 2018 9:35 PM
  • When possible, it really, really helps to provide sample code that compiles and executes and duplicates the problem. The sample you posted is close but would not compile.

    The line:

    objSendInput.Click(250, 250, False)

    Is a mistake, right? I don't know what the following is:

    Public Property objSendInput As SendInput With {.Process = something}

    I don't know VB.Net very well and I can't find anything showing "With" for a property. Is that some pseudocode? In other words, is that a comment? There are a few other things that are missing. I think the following would work except I got to the pseudocode and gave up.

    Imports System.Threading
    Imports System.Drawing
    
    Module Module1
    
        Sub Main()
            Dim x As New ThreadHandler
            x.Main()
            Dim y As New ThreadHandler
            y.Main()
        End Sub
    
    End Module
    
    Enum Resolution
        Half
        Full
    End Enum
    
    Class ThreadHandler
        Dim Number As Byte = 0
        Public Property objSendInput As SendInput With {.Process = something}
        Public Property processResolution As Resolution 'Resolution is a byte enumeration
        Public Property RunningThread As New Thread(AddressOf ThreadStart) With {.IsBackground = True, .Name = "runningThread" & Number}
    
        Sub Main()
            RunningThread.Start()
        End Sub
    
        Sub ThreadStart()
            Click()
        End Sub
    
        Private Sub Click()
            If processResolution = Resolution.Half Then
                'objSendInput.Click(250, 250, False)
                objSendInput.Click(250, 250)
            Else
                objSendInput.Click(500, 500)
            End If
        End Sub
    
    End Class
    
    Class SendInput
        Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Int32, ByVal wMsg As Int32, ByVal wParam As Int32, ByVal lParam As Int32) As IntPtr
        Public Declare Function GetWindowRect Lib "user32.dll" (ByVal hwnd As Int32, ByRef lpRect As Rectangle) As Boolean
        Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Int32
        Private Const WM_LBUTTONDOWN As UInt32 = &H201
        Private Const WM_LBUTTONUP As UInt32 = &H202
        Public Property RunningProcess As Process
    
        Sub Click(ByVal x As Short, ByVal y As Short)
            Dim MousePos As Long
            Dim hWind As Integer
            Dim rect As Rectangle
    
            If RunningProcess Is Nothing Then
                hWind = FindWindow(Nothing, "NotePad")
            Else
                hWind = RunningProcess.MainWindowHandle
            End If
            GetWindowRect(hWind, rect)
            x = rect.Width - rect.X
            y = rect.Height - rect.Y
    
            MousePos = MakeDWord(x, y)
            SendMessage(hWind, WM_LBUTTONDOWN, 0, MousePos)
            SendMessage(hWind, WM_LBUTTONUP, 0, MousePos)
        End Sub
    
        Function MakeDWord(ByVal LoWord As Integer, ByVal HiWord As Integer) As Long
            MakeDWord = (HiWord * &H10000) Or (LoWord And &HFFFF&)
        End Function
    
    End Class
    



    Sam Hobbs
    SimpleSamples.Info

    Wednesday, April 4, 2018 4:39 AM
  • Sam wrote already about a certain aspects of using threading. 

    Do you know what threading means in .Net. 

    It is a feature which can be used on a single core processor without any threading feature. 

    It is using the multitasking feature of the Windows OS. If you want to do synchronized multi threading, you're only creating horrible functioning programs.

     https://en.wikipedia.org/wiki/Computer_multitasking


    Success
    Cor




    Wednesday, April 4, 2018 7:27 AM
  • What is the point of the multithreading in this application?

    Why not just use Task.Run?  There's almost no reason to ever try handling multithreading manually.  

    Also, you should always put your unmanaged Win32 API declaration in a class called "NativeMethods".  Make all of the API declarations private and then expose them with managed public wrapper methods.  Something like:

    Option Strict On
    
    Imports System.Runtime.InteropServices
    
    Public NotInheritable Class NativeMethods
        Protected Sub New()
        End Sub
    
        Private Const WM_LBUTTONDOWN = &H201
        Private Const WM_LBUTTONUP = &H202
    
        Private Declare Auto Function GetWindowRect Lib "user32.dll" (hWnd As IntPtr, ByRef lpRect As RECT) As Integer
        Private Declare Unicode Function FindWindowW Lib "User32.dll" (className As String, windowTitle As String) As IntPtr
        Private Declare Auto Function SendMessage Lib "User32.dll" (ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As IntPtr
    
        Public Shared Function FindWindow(windowTitle As String) As IntPtr
            Dim mainWindowHandle = NativeMethods.FindWindowW(Nothing, windowTitle)
            Return mainWindowHandle
        End Function
    
        Public Shared Function GetWindowBounds(hWnd As IntPtr) As Rectangle
            Dim result As New RECT
            If Not GetWindowRect(hWnd, result) = 0 Then
                Return Rectangle.FromLTRB(result.Left, result.Top, result.Right, result.Bottom)
            Else
                Return Rectangle.Empty
            End If
        End Function
    
        Public Shared Sub MouseLeftClickAt(handle As IntPtr, x As Integer, y As Integer)
            Dim clickPoint = CType((y << 16) Or (x And &HFFFF), IntPtr)
            SendMessage(handle, WM_LBUTTONDOWN, CType(1, IntPtr), clickPoint)
            SendMessage(handle, WM_LBUTTONUP, CType(0, IntPtr), clickPoint)
        End Sub
    
        <StructLayout(LayoutKind.Sequential)>
        Private Structure RECT
            Public Left As Integer
            Public Top As Integer
            Public Right As Integer
            Public Bottom As Integer
        End Structure
    End Class
    


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Wednesday, April 4, 2018 3:44 PM
    Moderator
  • What is the point of the multithreading in this application?

    Why not just use Task.Run?  There's almost no reason to ever try handling multithreading manually.  

    Also, you should always put your unmanaged Win32 API declaration in a class called "NativeMethods".  Make all of the API declarations private and then expose them with managed public wrapper methods. 

    I wonder what the policy is on attempting to impose methodologies in forum threads. It seems to me that such things can erupt into unmanageable discussions. Some people think that some Windows Forms applications must have a message loop that calls Windows API functions using Platform Invoke. Other people have strong opinions that that should rarely if ever be done. So is the policy that we express opinions such as that?

    Personally I agree that it is good to attempt to help members to write better code that is object-oriented and organized but there is a difference between a helpful suggestion and imposing a standard that "should" be complied with.



    Sam Hobbs
    SimpleSamples.Info


    Wednesday, April 4, 2018 9:43 PM
  • I don't think there's really much question about the policy on promoting best practice.  The folks who create .Net say to use Tasks over raw threading; pretty much anyone who has ever written anything about threading in .Net will say the same thing today.  Its cleaner to write, easier to maintain and completely debuggable.

    From the MSDN Documentation:

    "...in the .NET Framework, TPL is the preferred API for writing multi-threaded, asynchronous, and parallel code. "


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Wednesday, April 4, 2018 10:09 PM
    Moderator
  • Oh wait, you are referring to the NativeMethods class?  You're right, that's convention.  I don't think there's harm in guiding folks toward convention.

    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Wednesday, April 4, 2018 10:16 PM
    Moderator
  • Oh wait, you are referring to the NativeMethods class?  You're right, that's convention.  I don't think there's harm in guiding folks toward convention.

    Then I'll say the word "should" can be interpreted as judgmental. I hope it is safe to say that.



    Sam Hobbs
    SimpleSamples.Info

    Thursday, April 5, 2018 12:09 AM
  • You are probably frustrated by everyone telling you what you have done wrong. I think you can ignore some of what we are saying at least.

    The following code works. This is just a skeleton of your skeleton but I have fixed a few things.

    Note the "<MTAThread>"; I don't know if that is necessary for the way you will do things but it is needed for the way I am doing things at least.

    I am using AutoResetEvent so that the console program can wait for the threads.

    "Number" was not being set properly.

    Imports System.Threading
    
    Module Module1
    
        <MTAThread>
        Sub Main()
            Dim DoneEvents(1) As AutoResetEvent
            '
            Dim x As New ThreadHandler(1)
            x.Main()
            DoneEvents(0) = x.DoneEvent
            Dim y As New ThreadHandler(2)
            y.Main()
            DoneEvents(1) = y.DoneEvent
            '
            WaitHandle.WaitAll(DoneEvents)
        End Sub
    
    End Module
    
    Class ThreadHandler
        Public Property RunningThread
        Public DoneEvent As New AutoResetEvent(False)
    
        Public Sub New(ByVal Number As Byte)
            RunningThread = New Thread(AddressOf ThreadStart) With {.IsBackground = True, .Name = "runningThread" & Number}
        End Sub
    
        Sub Main()
            RunningThread.Start()
        End Sub
    
        Sub ThreadStart()
            Console.WriteLine(RunningThread.Name + " done")
            DoneEvent.Set()
        End Sub
    
    End Class
    



    Sam Hobbs
    SimpleSamples.Info

    Thursday, April 5, 2018 1:55 AM