WEB项目中除了单元测试,还经常需要多线程测试一个方法是否存在并发问题,或者是否有性能问题。每次都要写测试代码总是一件很累的事情。于是写了这一个多线程测试的类库,用来进行快速的多线程并发测试。
ManualResetEvent。ManualResetEvent 类表示一个本地等待处理事件,在已发事件信号后必须手动重置该事件。通常,此通信涉及一个线程在其他线程进行之前必须完成的任务。当一个线程开始一个活动(此活动必须完成后,其他线程才能开始)时,它调用 Reset 以将 ManualResetEvent 置于非终止状态。此线程可被视为控制 ManualResetEvent。调用 ManualResetEvent 上的 WaitOne 的线程将阻止,并等待信号。当控制线程完成活动时,它调用 Set 以发出等待线程可以继续进行的信号。并释放所有等待线程。一旦它被终止,ManualResetEvent 将保持终止状态,直到它被手动重置。即对 WaitOne 的调用将立即返回。可以通过将布尔值传递给构造函数来控制 ManualResetEvent 的初始状态,如果初始状态处于终止状态,为 true;否则为 false。
多线程并发测试由以下步骤完成:
- 创建并发测试的线程数,先创建的线程等待最后一个线程创建完成。
- 所有线程执行待测试的方法,返回测试的结果。
- 等所有线程执行完成后,进入思考时间等待。
- 继续进行循环测试。
我们来看这个多线程并发测试的代码。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
/// <summary>
/// 并发测试
/// </summary>
public class ConcurrentTest : IDisposable
{
#region 私有方法
/// <summary>
/// 测试方法所在的接口
/// </summary>
private Func< bool > func;
/// <summary>
/// 主线程控制信号
/// </summary>
private ManualResetEvent manualResetEvent;
/// <summary>
/// 测试线程控制信号
/// </summary>
private ManualResetEvent threadResetEvent;
/// <summary>
/// 待执行的线程数
/// </summary>
private List< int > threads;
/// <summary>
/// 测试结果
/// </summary>
private List<ConcurrentTestResult> results;
/// <summary>
/// 执行测试的成功数
/// </summary>
private int successCount;
/// <summary>
/// 执行测试的失败数
/// </summary>
private int failureCount;
/// <summary>
/// 测试耗时
/// </summary>
private long elapsedMilliseconds;
/// <summary>
/// 当前线程
/// </summary>
private int currentIndex;
/// <summary>
/// 当前测试的总线程数
/// </summary>
private int currentCount;
/// <summary>
/// 思考时间
/// </summary>
private int thinkTime;
/// <summary>
/// 重复次数
/// </summary>
private int repeatCount;
/// <summary>
/// 测试计时器
/// </summary>
private Stopwatch stopwatch;
#endregion
#region 构造函数
/// <summary>
/// 构造函数
/// </summary>
public ConcurrentTest()
{
manualResetEvent = new ManualResetEvent( true );
threadResetEvent = new ManualResetEvent( true );
stopwatch = new Stopwatch();
}
#endregion
#region 执行测试
/// <summary>
/// 执行多线程测试
/// </summary>
/// <param name="threadCount">需要测试的线程数</param>
/// <param name="func">待执行方法</param>
/// <returns></returns>
public List<ConcurrentTestResult> Execute( int threadCount, Func< bool > func)
{
return Execute(threadCount, 1, func);
}
/// <summary>
/// 执行多线程测试
/// </summary>
/// <param name="threadCount">需要测试的线程数</param>
/// <param name="repeatCount">重复次数</param>
/// <param name="func">待执行方法</param>
/// <returns></returns>
public List<ConcurrentTestResult> Execute( int threadCount, int repeatCount, Func< bool > func)
{
return Execute(threadCount, 0, repeatCount, func);
}
/// <summary>
/// 执行多线程测试
/// </summary>
/// <param name="threadCount">需要测试的线程数</param>
/// <param name="thinkTime">思考时间,单位耗秒</param>
/// <param name="repeatCount">重复次数</param>
/// <param name="func">待执行方法</param>
/// <returns></returns>
public List<ConcurrentTestResult> Execute( int threadCount, int thinkTime, int repeatCount, Func< bool > func)
{
return Execute( new List< int >() { threadCount }, thinkTime, repeatCount, func);
}
/// <summary>
/// 执行多线程测试
/// </summary>
/// <param name="threads">分别需要测试的线程数</param>
/// <param name="thinkTime">思考时间,单位耗秒</param>
/// <param name="repeatCount">重复次数</param>
/// <param name="func">待执行方法</param>
/// <returns></returns>
public List<ConcurrentTestResult> Execute(List< int > threads, int thinkTime, int repeatCount, Func< bool > func)
{
this .func = func;
this .threads = threads;
this .thinkTime = thinkTime;
this .repeatCount = repeatCount;
CheckParameters();
CreateMultiThread();
return this .results;
}
#endregion
#region 验证参数
/// <summary>
/// 验证参数
/// </summary>
private void CheckParameters()
{
if (func == null ) throw new ArgumentNullException( "func不能为空" );
if (threads == null || threads.Count == 0) throw new ArgumentNullException( "threads不能为空或者长度不能为0" );
if (thinkTime < 0) throw new Exception( "thinkTime不能小于0" );
if (repeatCount <= 0) throw new Exception( "repeatCount不能小于等于0" );
}
#endregion
#region 创建多线程并执行测试
/// <summary>
/// 创建多线程进行测试
/// </summary>
private void CreateMultiThread()
{
results = new List<ConcurrentTestResult>(threads.Count);
foreach ( int threadCount in threads)
{
for ( int repeat = 0; repeat < repeatCount; repeat++)
{
//主线程进入阻止状态
manualResetEvent.Reset();
//测试线程进入阻止状态
threadResetEvent.Reset();
stopwatch.Reset();
currentCount = threadCount;
currentIndex = 0;
successCount = 0;
failureCount = 0;
elapsedMilliseconds = 0;
for ( int i = 0; i < currentCount; i++)
{
Thread t = new Thread( new ThreadStart(DoWork));
t.Start();
}
//阻止主线程,等待测试线程完成测试
manualResetEvent.WaitOne();
results.Add( new ConcurrentTestResult()
{
FailureCount = failureCount,
SuccessCount = successCount,
ElapsedMilliseconds = elapsedMilliseconds
});
Thread.Sleep(thinkTime);
}
}
}
/// <summary>
/// 执行测试方法
/// </summary>
private void DoWork()
{
bool executeResult;
Interlocked.Increment( ref currentIndex);
if (currentIndex < currentCount)
{
//等待所有线程创建完毕后同时执行测试
threadResetEvent.WaitOne();
}
else
{
//最后一个线程创建完成,通知所有线程,开始执行测试
threadResetEvent.Set();
//开始计时
stopwatch.Start();
}
//执行测试
executeResult = func();
Interlocked.Decrement( ref currentIndex);
if (currentIndex == 0)
{
//最后一个线程执行的测试结束,结束计时
stopwatch.Stop();
elapsedMilliseconds = stopwatch.ElapsedMilliseconds;
//保存测试结果
if (executeResult)
Interlocked.Increment( ref successCount);
else
Interlocked.Increment( ref failureCount);
//通知主线程继续
manualResetEvent.Set();
}
else
{
//保存测试结果
if (executeResult)
Interlocked.Increment( ref successCount);
else
Interlocked.Increment( ref failureCount);
}
}
#endregion
#region 释放资源
/// <summary>
/// 释放资源
/// </summary>
public void Dispose()
{
manualResetEvent.Close();
threadResetEvent.Close();
}
#endregion
}
/// <summary>
/// 并发测试结果
/// </summary>
public class ConcurrentTestResult
{
/// <summary>
/// 当前执行线程总数
/// </summary>
public int ThreadCount
{
get { return SuccessCount + FailureCount; }
}
/// <summary>
/// 测试成功数
/// </summary>
public int SuccessCount { get ; set ; }
/// <summary>
/// 测试失败数
/// </summary>
public int FailureCount { get ; set ; }
/// <summary>
/// 总耗时
/// </summary>
public long ElapsedMilliseconds { get ; set ; }
}
使用起来就非常简单了,我们看测试代码:
class Program
{
static void Main( string [] args)
{
using (ConcurrentTest concurrentTest = new ConcurrentTest())
{
var result = concurrentTest.Execute(5, -1, 10, new TestClass().Execute);
foreach (var item in result)
{
Console.WriteLine( "线程数:{0}\t成功:{1}\t失败:{2}\t耗时:{3}" ,
item.ThreadCount, item.SuccessCount, item.FailureCount, item.ElapsedMilliseconds);
}
}
Console.ReadKey( true );
}
}
public class TestClass
{
public bool Execute()
{
int tempValue = GetRandom();
System.Threading.Thread.Sleep(tempValue);
return tempValue % 2 == 0;
}
private int GetRandom()
{
return new Random().Next(990, 1000);
}
}
测试类库提供了4个Execute方法的重载,一般情况下能满足我们的多线程并发测试场景了。