如何在C#中以特定吞吐量调用我的方法?
我正在做一些性能测试,所以希望以我可以控制的特定吞吐量调用我的方法。通过这种方式,我可以生成一些负载,并了解它在不同吞吐量下的行为 例如:我需要调用我的如何在C#中以特定吞吐量调用我的方法?,c#,multithreading,performance-testing,C#,Multithreading,Performance Testing,我正在做一些性能测试,所以希望以我可以控制的特定吞吐量调用我的方法。通过这种方式,我可以生成一些负载,并了解它在不同吞吐量下的行为 例如:我需要调用我的doIOStuff方法,从多个线程以大约每秒x个请求的速率调用,其中x通常小于2000个,但在这种情况下这并不重要。它不必精确,因此存在一些出错的空间,但总体思路是我需要确保我的方法doIOStuff在y秒的滑动窗口中执行不超过x次 下面是我得到的代码,我正在创建3个不同的线程,并行调用doIOStuff方法,但无法计算如何将此方法的速率限制为每
doIOStuff
方法,从多个线程以大约每秒x个请求的速率调用,其中x通常小于2000个,但在这种情况下这并不重要。它不必精确,因此存在一些出错的空间,但总体思路是我需要确保我的方法doIOStuff
在y秒的滑动窗口中执行不超过x次
下面是我得到的代码,我正在创建3个不同的线程,并行调用doIOStuff
方法,但无法计算如何将此方法的速率限制为每秒x个请求。有没有一个简单的方法可以控制这个
class Program
{
static void Main(string[] args)
{
var tasks = new List<Task>();
for(int i = 0; i< 100; i ++)
tasks.Add(Task.Factory.StartNew(() => doIOStuff(), i));
Task.WaitAll(tasks.ToArray());
Console.WriteLine("All threads complete");
}
// how to call this method at a particular rate?
static void doIOStuff()
{
// do some IO work
}
}
类程序
{
静态void Main(字符串[]参数)
{
var tasks=新列表();
对于(int i=0;i<100;i++)
tasks.Add(Task.Factory.StartNew(()=>doIOStuff(),i));
Task.WaitAll(tasks.ToArray());
Console.WriteLine(“所有线程完成”);
}
//如何以特定速率调用此方法?
静态void doIOStuff()
{
//做一些IO工作
}
}
我希望在一段时间内继续运行此测试,但在这段时间内,它应该始终仅以特定吞吐量调用该方法
注意:
这只是我的一个想法,即生成随机吞吐量,我可以控制它,但如果这不能正确完成任务,那么我们应该尽可能以更好、更高效的方式来实现,但总体想法是自己控制随机吞吐量,并为我的方法生成如此大的负载。假设您启动n个线程,并希望每秒最多有m个呼叫。这可以通过让每个线程每秒生成0到1k次的随机数来实现,并且仅当生成的数字小于m/n/k时才调用IO方法
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading;
namespace ConsoleApp7
{
class Program
{
const int m_threads = 100;
const int n_throughput = 2000;
const int k_toss_per_second = 2000; // Note that k_toss_per_second x m_threads >= n_throughput
static void Main(string[] args)
{
var tasks = new List<Task>();
for (int i = 0; i < m_threads; i++)
tasks.Add(Task.Factory.StartNew(() => callDoIOStuff()));
Task.WaitAll(tasks.ToArray());
Console.WriteLine("All threads complete");
}
static void callDoIOStuff()
{
int sleep_time = (int) (1000 * 1.0d / k_toss_per_second);
double threshold = (double) n_throughput / m_threads / k_toss_per_second;
Random random = new Random();
while (true) {
Thread.Sleep(sleep_time);
if (random.NextDouble() < threshold)
doIOStuff();
}
}
// how to call this method at a particular rate?
static void doIOStuff()
{
// do some IO work
}
}
}
使用系统;
使用System.Collections.Generic;
使用System.Threading.Tasks;
使用系统线程;
名称空间控制台App7
{
班级计划
{
const int m_线程=100;
const int n_吞吐量=2000;
const int k_toss_per_second=2000;//注意k_toss_per_second x m_线程>=n_吞吐量
静态void Main(字符串[]参数)
{
var tasks=新列表();
对于(int i=0;icallDoIOStuff());
Task.WaitAll(tasks.ToArray());
Console.WriteLine(“所有线程完成”);
}
静态void callDoIOStuff()
{
int sleep_time=(int)(1000*1.0d/k_toss_/s);
双阈值=(双)n_吞吐量/m_线程/k_丢弃率/u秒;
随机=新随机();
while(true){
睡眠(睡眠时间);
if(random.NextDouble()<阈值)
doIOStuff();
}
}
//如何以特定速率调用此方法?
静态void doIOStuff()
{
//做一些IO工作
}
}
}
您需要注意传递取消令牌和任何参数。使用全局变量时,此代码示例既快速又脏,可以大大改进。一种方法是在给定的时间段内执行它
创建表示DoIOStuff
方法的Action
类型的2000个实例。然后尝试使用此操作启动2000个任务,但只有您才能在1秒内完成(TakeWhile
)
var period=TimeSpan.FromSeconds(1);
var maxAmountOfCalls=2000;
var methods=Enumerable.Range(0,maxAmountOfCalls)
.选择(=>doIOStuff)
.ToArray();
var watch=新秒表();
watch.Start();
var任务=方法
.Select(方法=>Task.Run(方法))
.TakeWhile(任务=>watch.appeased
如果时间段较短,此方法开始时将少于所需的量。以下是一种方法,可用于对异步doIOStuffAsync
方法进行压力测试:
public static async Task<long> StressTestAsync(
Func<CancellationToken, Task> taskFactory,
TimeSpan duration,
int concurrencyLimit,
int tasksStartedPerSecondLimit,
IProgress<long> progress = default,
CancellationToken cancellationToken = default)
{
long successfullyCompletedCount = 0;
using (var linkedCTS = CancellationTokenSource
.CreateLinkedTokenSource(cancellationToken))
using (var progressTimer = new System.Threading.Timer(_ =>
{
progress.Report(Interlocked.Read(ref successfullyCompletedCount));
}))
{
var concurrencySemaphore = new SemaphoreSlim(concurrencyLimit);
var perSecondSemaphore = new SemaphoreSlim(tasksStartedPerSecondLimit);
var completionSemaphore = new SemaphoreSlim(0, 1);
int pendingCount = 1; // The initial 1 represents the while loop
var exceptions = new ConcurrentQueue<Exception>();
linkedCTS.CancelAfter(duration);
if (progress != null)
progressTimer.Change(1000, 1000); // Report progress every second
while (true)
{
try
{
await concurrencySemaphore.WaitAsync(linkedCTS.Token)
.ConfigureAwait(false);
await perSecondSemaphore.WaitAsync(linkedCTS.Token)
.ConfigureAwait(false);
}
catch (OperationCanceledException) { break; }
ReleaseSemaphoreAfterOneSecond();
StartOneTask();
}
if (Interlocked.Decrement(ref pendingCount) == 0)
completionSemaphore.Release();
await completionSemaphore.WaitAsync().ConfigureAwait(false); // No token
cancellationToken.ThrowIfCancellationRequested();
if (!exceptions.IsEmpty) throw new AggregateException(exceptions);
async void ReleaseSemaphoreAfterOneSecond()
{
try
{
await Task.Delay(1000, linkedCTS.Token).ConfigureAwait(false);
}
catch (OperationCanceledException) { } // Ignore
finally
{
perSecondSemaphore.Release();
}
}
async void StartOneTask()
{
Interlocked.Increment(ref pendingCount);
try
{
var task = taskFactory(linkedCTS.Token);
await task.ConfigureAwait(false);
Interlocked.Increment(ref successfullyCompletedCount);
}
catch (OperationCanceledException) { } // Ignore
catch (Exception ex)
{
exceptions.Enqueue(ex);
linkedCTS.Cancel();
}
finally
{
if (Interlocked.Decrement(ref pendingCount) == 0)
completionSemaphore.Release();
concurrencySemaphore.Release();
}
}
}
return Interlocked.Read(ref successfullyCompletedCount);
}
concurrencyLit
参数是在任何给定时刻可以并发运行的最大任务数。tasksStartedPerSecondLimit
参数是任何1秒时间跨度内可以启动的最大任务数。这两个限制相互竞争,因此通常只有其中一个限制了吞吐量。如果任务很快,并发限制将是限制因素。如果任务速度较慢,则限制因素将是tasksStartedPerSecondLimit
StressTestAsync
不允许出现异常。taskFactory
方法引发的任何异常,或者如果任何创建的任务在故障状态下完成,都将导致测试终止
A可以作为可选参数传递,以传播有关当前成功完成任务数的进度报告。这意味着它要向doIOStuff
方法发送2000个qps?如果我需要改变这一点,那么我可以将2000
切换到任何能给我带来那么多qps的数字。正确吗?这与每次调用之间睡眠时间为1/2000秒的简单循环有多大不同?@Tarik,只是使用此方法,我不需要显式创建空任务集合,然后将任务添加到循环中的集合中。代码与描述不匹配。真正发生的是,2000个任务立即排队,它们执行所需的时间没有定义。(这也是一种非常迂回的方式
public static async Task<long> StressTestAsync(
Func<CancellationToken, Task> taskFactory,
TimeSpan duration,
int concurrencyLimit,
int tasksStartedPerSecondLimit,
IProgress<long> progress = default,
CancellationToken cancellationToken = default)
{
long successfullyCompletedCount = 0;
using (var linkedCTS = CancellationTokenSource
.CreateLinkedTokenSource(cancellationToken))
using (var progressTimer = new System.Threading.Timer(_ =>
{
progress.Report(Interlocked.Read(ref successfullyCompletedCount));
}))
{
var concurrencySemaphore = new SemaphoreSlim(concurrencyLimit);
var perSecondSemaphore = new SemaphoreSlim(tasksStartedPerSecondLimit);
var completionSemaphore = new SemaphoreSlim(0, 1);
int pendingCount = 1; // The initial 1 represents the while loop
var exceptions = new ConcurrentQueue<Exception>();
linkedCTS.CancelAfter(duration);
if (progress != null)
progressTimer.Change(1000, 1000); // Report progress every second
while (true)
{
try
{
await concurrencySemaphore.WaitAsync(linkedCTS.Token)
.ConfigureAwait(false);
await perSecondSemaphore.WaitAsync(linkedCTS.Token)
.ConfigureAwait(false);
}
catch (OperationCanceledException) { break; }
ReleaseSemaphoreAfterOneSecond();
StartOneTask();
}
if (Interlocked.Decrement(ref pendingCount) == 0)
completionSemaphore.Release();
await completionSemaphore.WaitAsync().ConfigureAwait(false); // No token
cancellationToken.ThrowIfCancellationRequested();
if (!exceptions.IsEmpty) throw new AggregateException(exceptions);
async void ReleaseSemaphoreAfterOneSecond()
{
try
{
await Task.Delay(1000, linkedCTS.Token).ConfigureAwait(false);
}
catch (OperationCanceledException) { } // Ignore
finally
{
perSecondSemaphore.Release();
}
}
async void StartOneTask()
{
Interlocked.Increment(ref pendingCount);
try
{
var task = taskFactory(linkedCTS.Token);
await task.ConfigureAwait(false);
Interlocked.Increment(ref successfullyCompletedCount);
}
catch (OperationCanceledException) { } // Ignore
catch (Exception ex)
{
exceptions.Enqueue(ex);
linkedCTS.Cancel();
}
finally
{
if (Interlocked.Decrement(ref pendingCount) == 0)
completionSemaphore.Release();
concurrencySemaphore.Release();
}
}
}
return Interlocked.Read(ref successfullyCompletedCount);
}
await StressTestAsync(
taskFactory: async ct => await doIOStuffAsync(ct),
duration: TimeSpan.FromSeconds(30),
concurrencyLimit: 1000,
tasksStartedPerSecondLimit: 1000);