【问题标题】:Multiple threads acces same function at same time多个线程同时访问同一个函数
【发布时间】:2017-05-20 14:14:35
【问题描述】:

我有一个控制台应用程序,它使用多个线程来处理项目列表。所有这些项目都需要访问一个 API,我不想每秒向该 API 发出超过 1 个请求。

所以我有一个执行 API 请求的类

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

我有主程序

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

但是如果两个线程同时执行一个请求会发生什么,它们都通过了秒检查,因为它们首先执行了这行代码

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

在他们到达这条线之前

 lastRequest = now()

如何使 apiRequest 函数每秒只有 1 个请求?

解决方案

我使用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

这与 Monitor.Enter 中的 Monitor.EnterMonitor.Exit answer 几乎相同,除了 lock 和 SyncLock 将 Exit 方法包装在 try...finally 块中(在 Visual Basic 中为 Try...Finally)以确保监视器被释放(正如 Alex B 在 cmets 中指出的那样)

【问题讨论】:

  • 另一种方法是将项目添加到 ConcurrentQueue 并在计时器滴答时将它们出列。这样,您可以随时创建请求,但它们将按照您设置的速率提交,并且不需要锁定任何内容。

标签: .net vb.net multithreading


【解决方案1】:

这将禁止每秒发生多个请求。它假设这个

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

想要等待其他请求。您的课程已修改。

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

【讨论】:

  • 谢谢!这与使用 synclock APILock/end synclock(我在此期间发现并且似乎有效)相同吗?
  • @NLAnaconda - 如果您找到了解决方案,请发布。 (编辑您的原始帖子,将您的解决方案添加到末尾)
  • MSDN:The functionality provided by the Enter and Exit methods is identical to that provided by the C# lock statement (SyncLock in Visual Basic), 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.
【解决方案2】:

您可以将项目添加到 ConcurrentQueue 并在计时器滴答时将它们从队列中取出。这样,您可以随时创建请求,但它们将按照您设置的速率提交,并且不需要锁定任何内容。

例如,

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

显示正在处理的项目的示例输出,即使更多项目正在排队:

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

有时文本的颜色(此处未说明)是错误的,因为设置控制台颜色的线程在编写文本之前被中断。将文本写入控制台也是并发队列的一个很好的候选者,但它会妨碍此示例代码的清晰性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多