NLAnaconda
NLAnaconda

Reputation: 1595

Multiple threads acces same function at same time

I have a console application which uses multiple threads to work trough a list of items. All these items need to access an API and I don't want to make more then 1 request a second to that API.

So I have a class which does the API request

Public Class apiRequest
  Property lastRequest as date = now()

  function doRequest()
      'check if we can make a request
      if Now.Substract(lastRequest).Seconds > 1 then
         lastRequest = now()

         'Do request [...]
      end if
  end function
End Class

And I have the main program

property apiRequester as new apiRequest

sub main
    'start multiple threads (addressOf threadFunction)[...]
end sub

sub threadFunction()
  dim data as string = apirequester.doRequest() 'Call the api request
end sub

But what happens if two threads do a request exactly at the same time, they both come past de seconds check because they execute both this line first

if Now.Substract(lastRequest).Seconds > 1 then

before any of them get to this line

 lastRequest = now()

How can I make the apiRequest function so there is only 1 request a second?

Solution

I found the following solution using SyncLock.

Private locker As New Object
Private lastRequest as date = now()

function doRequest()

  SyncLock locker
    if Now.Substract(lastRequest).Seconds > 1 then
      lastRequest = now()
    end if
  End Synclock

end function

This works almost the same as the Monitor.Enter, Monitor.Exit answer from dbasnett except that lock and SyncLock wrap the Exit method in a try…finally block (Try…Finally in Visual Basic) to ensure that the monitor is released (as Alex B pointed out in the comments)

Upvotes: 0

Views: 1414

Answers (2)

Andrew Morton
Andrew Morton

Reputation: 25013

You could add items to a ConcurrentQueue and de-queue them on a timer tick. That way you can create a request at any time but they will be submitted at the rate you set and there is no locking of anything required.

For example,

Option Infer On

Imports System.Collections.Concurrent
Imports System.Timers

Module Module1

    Class ApiRequest
        Property SerialNo As Integer
    End Class

    Dim q As ConcurrentQueue(Of ApiRequest)
    Dim tim As Timer

    Sub Dequeue(sender As Object, e As ElapsedEventArgs)
        Dim ar As ApiRequest = Nothing
        If q.TryDequeue(ar) Then
            Console.ForegroundColor = ConsoleColor.Green
            Console.WriteLine($"Processed: {ar.SerialNo}")
        End If

    End Sub

    Sub Init()
        q = New ConcurrentQueue(Of ApiRequest)
        tim = New Timer With {.AutoReset = True, .Interval = 1000}
        AddHandler tim.Elapsed, AddressOf Dequeue
        tim.Start()

    End Sub

    Sub Main()
        Init()

        For i = 1 To 20
            Dim ar = New ApiRequest With {.SerialNo = i}
            q.Enqueue(ar)
            Console.ForegroundColor = ConsoleColor.Red
            Console.WriteLine($"Created: {ar.SerialNo}")
            Threading.Thread.Sleep(200)
        Next

        Console.ForegroundColor = ConsoleColor.Yellow
        Console.WriteLine("<press any key to exit>")

        ' wait for user
        Console.Read()

        ' clean up
        tim.Stop()
        RemoveHandler tim.Elapsed, AddressOf Dequeue
        tim.Dispose()

    End Sub

End Module

Sample output which shows items being processed even as more items are beining enqueued:

Created: 1
Created: 2
Created: 3
Created: 4
Created: 5
Created: 6
Processed: 1
Created: 7
Created: 8
Created: 9
Created: 10
Processed: 2
Created: 11
Created: 12
Created: 13
Created: 14
Created: 15
Processed: 3
Created: 16
Created: 17
Created: 18
Created: 19
Created: 20
Processed: 4
<press any key to exit>
Processed: 5
Processed: 6
Processed: 7
Processed: 8
Processed: 9
Processed: 10
Processed: 11
Processed: 12
Processed: 13
Processed: 14
Processed: 15
Processed: 16
Processed: 17
Processed: 18
Processed: 19
Processed: 20

Sometimes the colours of the text (not illustratable here) are wrong because the thread that sets the console colour is interrupted before the text is written. Writing the text to the console would also be a good candidate for a concurrentqueue, but it would hinder the clarity this example code.

Upvotes: 0

dbasnett
dbasnett

Reputation: 11773

This will prohibit more than one request from happening per second. It assumes that this

dim data as string = apirequester.doRequest() 'Call the api request

wants to wait for other requests. Your class modified.

Public Class apiRequest
    Private APILock As New Object
    Private stpw As Stopwatch = Stopwatch.StartNew
    Private Shared ReadOnly ReqEvery As New TimeSpan(0, 0, 1)

    Function doRequest() As Boolean
        'check if we can make a request
        Threading.Monitor.Enter(Me.APILock) 'only one request past this point at a time
        Do While Me.stpw.Elapsed < ReqEvery 'loop until one second has passed
            Threading.Thread.Sleep(10)
        Loop

        'Do request [...]

        Me.stpw.Restart() 'restart the clock
        Threading.Monitor.Exit(Me.APILock) 'allow other requests
    End Function
End Class

Upvotes: 1

Related Questions