帮助从 C# 单元测试进行同步

发布于 2024-10-31 19:36:59 字数 3130 浏览 0 评论 0原文

我正在测试一个包装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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

纵情客 2024-11-07 19:36:59

我认为这个修订版是一个改进,阻止了测试线程并使用 AutoResetEvent 发出信号。但在测试可读性方面没有赢得任何奖励分。

    [Test]
    [Timeout(1250)]
    public void Execute()
    {
        var locker = new object();
        EventWaitHandle waitHandle = new AutoResetEvent(false);// <--
        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++;
                waitHandle.Set();// <--
            }
        };

        asyncExecutor.Execute(tester);

        while (true)
        {
            waitHandle.WaitOne();// <--
            if (progressEventCount < numberOfEvents) continue;
            Assert.Pass("Succeeded after {0} milliseconds", (DateTime.Now - start).TotalMilliseconds);
        }
    }

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.

    [Test]
    [Timeout(1250)]
    public void Execute()
    {
        var locker = new object();
        EventWaitHandle waitHandle = new AutoResetEvent(false);// <--
        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++;
                waitHandle.Set();// <--
            }
        };

        asyncExecutor.Execute(tester);

        while (true)
        {
            waitHandle.WaitOne();// <--
            if (progressEventCount < numberOfEvents) continue;
            Assert.Pass("Succeeded after {0} milliseconds", (DateTime.Now - start).TotalMilliseconds);
        }
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文