帮助从 C# 单元测试进行同步
我正在测试一个包装BackgroundWorker 的类,以在应用程序中远离UI 线程执行操作。
如果超过 Timeout,下面的测试将失败;如果 ProgressEventCount 在此之前达到预期的事件数,则下面的测试将通过。
我的问题是关于同步。 asyncExecutor.Progressed 从BackgroundWorker 正在使用的线程池线程中触发,测试线程在while 循环中将其读回。
我是否正确使用了锁?
[Test]
[Timeout(1250)]
public void Execute()
{
var locker = new object();
const int numberOfEvents = 10;
const int frequencyOfEvents = 100;
var start = DateTime.Now;
int progressEventCount = 0;
IGradualOperation tester = new TestGradualOperation(numberOfEvents, frequencyOfEvents);
var asyncExecutor = new AsynchronousOperationExecutor();
asyncExecutor.Progressed += (s, e) => { lock (locker) progressEventCount++; };
asyncExecutor.Execute(tester);
while (true)
{
int count;
lock (locker)
{
count = progressEventCount;
}
if (count < numberOfEvents) continue;
Assert.Pass("Succeeded after {0} milliseconds", (DateTime.Now - start).TotalMilliseconds);
}
}
// Implementation
public class AsynchronousOperationExecutor
{
public void Execute(IGradualOperation gradualOperation)
{
var backgroundWorker = new BackgroundWorker {WorkerReportsProgress = true};
backgroundWorker.DoWork += BackgroundWorkerDoWork;
backgroundWorker.ProgressChanged += BackgroundWorkerProgressChanged;
backgroundWorker.RunWorkerAsync(gradualOperation);
}
private void BackgroundWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
var myArgs = e.UserState as ProgressEventArgs;
OnProgressed(myArgs);
}
static void BackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
var workerThis = sender as BackgroundWorker;
var operation = e.Argument as IGradualOperation;
if (workerThis == null || operation == null) return;
operation.Progressed += (s, e1) => workerThis.ReportProgress((int)e1.Percentage, e1);
operation.Run();
}
private void OnProgressed(ProgressEventArgs e)
{
if (Progressed != null)
Progressed(this, e);
}
public event EventHandler<ProgressEventArgs> Progressed;
}
// Test Helper Class
public class TestGradualOperation : IGradualOperation
{
private readonly int _numberOfEvents;
private readonly int _frequencyMilliseconds;
public TestGradualOperation(int numberOfEvents, int frequencyMilliseconds)
{
_numberOfEvents = numberOfEvents;
_frequencyMilliseconds = frequencyMilliseconds;
}
public void Run()
{
for (int i = 0; i < _numberOfEvents; i++)
{
Thread.Sleep(_frequencyMilliseconds);
OnProgressed(new ProgressEventArgs(i, _numberOfEvents));
}
}
private void OnProgressed(ProgressEventArgs e)
{
if (Progressed != null)
Progressed(this, e);
}
public event EventHandler<ProgressEventArgs> Progressed;
}
I'm testing a class that wraps BackgroundWorker to perform an operation away from the UI thread in my application.
The test below fails if Timeout is exceeded and passes if progressEventCount reaches the expected number of events before then.
My question is about synchronization. asyncExecutor.Progressed is fired from the Thread Pool thread that BackgroundWorker is using and the test thread reads it back in the while loop.
Am I using lock correctly?
[Test]
[Timeout(1250)]
public void Execute()
{
var locker = new object();
const int numberOfEvents = 10;
const int frequencyOfEvents = 100;
var start = DateTime.Now;
int progressEventCount = 0;
IGradualOperation tester = new TestGradualOperation(numberOfEvents, frequencyOfEvents);
var asyncExecutor = new AsynchronousOperationExecutor();
asyncExecutor.Progressed += (s, e) => { lock (locker) progressEventCount++; };
asyncExecutor.Execute(tester);
while (true)
{
int count;
lock (locker)
{
count = progressEventCount;
}
if (count < numberOfEvents) continue;
Assert.Pass("Succeeded after {0} milliseconds", (DateTime.Now - start).TotalMilliseconds);
}
}
// Implementation
public class AsynchronousOperationExecutor
{
public void Execute(IGradualOperation gradualOperation)
{
var backgroundWorker = new BackgroundWorker {WorkerReportsProgress = true};
backgroundWorker.DoWork += BackgroundWorkerDoWork;
backgroundWorker.ProgressChanged += BackgroundWorkerProgressChanged;
backgroundWorker.RunWorkerAsync(gradualOperation);
}
private void BackgroundWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
var myArgs = e.UserState as ProgressEventArgs;
OnProgressed(myArgs);
}
static void BackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
var workerThis = sender as BackgroundWorker;
var operation = e.Argument as IGradualOperation;
if (workerThis == null || operation == null) return;
operation.Progressed += (s, e1) => workerThis.ReportProgress((int)e1.Percentage, e1);
operation.Run();
}
private void OnProgressed(ProgressEventArgs e)
{
if (Progressed != null)
Progressed(this, e);
}
public event EventHandler<ProgressEventArgs> Progressed;
}
// Test Helper Class
public class TestGradualOperation : IGradualOperation
{
private readonly int _numberOfEvents;
private readonly int _frequencyMilliseconds;
public TestGradualOperation(int numberOfEvents, int frequencyMilliseconds)
{
_numberOfEvents = numberOfEvents;
_frequencyMilliseconds = frequencyMilliseconds;
}
public void Run()
{
for (int i = 0; i < _numberOfEvents; i++)
{
Thread.Sleep(_frequencyMilliseconds);
OnProgressed(new ProgressEventArgs(i, _numberOfEvents));
}
}
private void OnProgressed(ProgressEventArgs e)
{
if (Progressed != null)
Progressed(this, e);
}
public event EventHandler<ProgressEventArgs> Progressed;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我认为这个修订版是一个改进,阻止了测试线程并使用 AutoResetEvent 发出信号。但在测试可读性方面没有赢得任何奖励分。
I think this revision is an improvement, blocking the test thread and signalling with an AutoResetEvent. Not winning any brownie points for test readability though.