Если вы хотите ограничить частоту вызовов чем-то в миллисекундном диапазоне, вам придется использовать вызов Win32, чтобы получить метку времени с более высоким разрешением, чем обычно доступно при использовании System.DateTime
.Я бы, вероятно, использовал QueryUnbiasedInterruptTime
, чтобы получить время с шагом 100 нс.Затем вы могли бы просто отслеживать последний раз, когда вы сделали вызов, и асинхронно спать, пока не истечет интервал, используя блокировку для синхронизации обновлений с временем последнего вызова:
open System
open System.Runtime.InteropServices
open System.Runtime.Versioning
open System.Threading
// Wrap the Win32 call to get the current time with 1-millisecond resolution
module private Timestamp =
[<DllImport("kernel32.dll")>]
[<ResourceExposure(ResourceScope.None)>]
extern bool QueryUnbiasedInterruptTime (int64& value)
let inline private queryUnbiasedInterruptTime () =
let mutable ticks = 0L
if QueryUnbiasedInterruptTime &ticks
then Some ticks
else None
/// Get the current timestamp in milliseconds
let get () =
match queryUnbiasedInterruptTime() with
| Some ticks -> ticks / 1000L
| _ -> DateTime.UtcNow.Ticks / TimeSpan.TicksPerMillisecond
// Stub for request and response types
type Request = Request
type Response = Response
// Minimum time between calls (ms) =
let callFrequencyThreshold = 10L
// A wrapper around the call to the service that will throttle the requests
let doWorkAsync : Request -> Async<Response> =
// Milliseconds since last call to service
let mutable lastCallTime = 0L
// Lock to protect the last call time
let syncRoot = obj()
// The real call to the service
let callService request =
async {
// Simulate work
do! Async.Sleep 1000
return Response
}
// Accept each request and wait until the threshold has elapsed to call the service
fun request ->
async {
let rec run () =
lock syncRoot <| fun () ->
async {
let currentTime = Timestamp.get()
if currentTime - lastCallTime > callFrequencyThreshold
then lastCallTime <- currentTime
return! callService request
else do! Async.Sleep <| int (callFrequencyThreshold - (currentTime - lastCallTime))
return! run ()
}
return! run ()
}
Однако я бы не сталрекомендовать основанный на времени подход к регулированию, если это абсолютно необходимо.Лично я предпочел бы что-то вроде семафора, чтобы ограничить количество одновременных вызовов к сервису.Таким образом, вы можете обеспечить только один вызов службы за раз, если это требуется, или разрешить настраиваемые n
вызовы службы за раз в зависимости от среды и т. Д. Это также значительно упрощает код, и вНа мой взгляд, дает более надежную реализацию:
open System.Threading
// Stub for request and response types
type Request = Request
type Response = Response
// A wrapper around the call to the service that will throttle the requests
let doWorkAsync : Request -> Async<Response> =
// A semaphore to limit the number of concurrent calls
let concurrencyLimit = 10
let semaphore = new SemaphoreSlim(concurrencyLimit, concurrencyLimit)
// The real call to the service
let callService request =
async {
// Simulate work
do! Async.Sleep 1000
return Response
}
// Accept each request, wait for a semaphore token to be available,
// then call the service
fun request ->
async {
do! semaphore.WaitAsync() |> Async.AwaitTask
return! callService request
}