繁体   English   中英

如何在任务返回之前延迟我的任务?

[英]How can I delay my task before it returns?

我对acceptor.IsStarted.Should().BeTrue();断言 (参见下面的单元测试)总是失败,因为它过早地被评估。 await task的调用会立即返回,并且没有给this.acceptor.Start()足够的时间来启动。

我想让FixAcceptor()的启动更具确定性,因此引入了参数TimeSpan startupDelay

但是,我根本不知道在哪里以及如何延迟启动。

this.acceptor.Start()this.IsStarted = true之间放置一个额外的Thread.Sleep(startupDelay)不会有帮助,因为它只会阻塞工作任务本身,而不是调用线程。

我希望很清楚我想要存档的内容以及我正在努力解决的问题。 提前致谢。

public class FixAcceptor
{
    // Type provided by QuickFix.net
    private readonly ThreadedSocketAcceptor acceptor;

    public FixAcceptor(IFixSettings settings)
    {
        // Shortened
    }

    public bool IsStarted { get; private set; }

    public async void Run(CancellationToken cancellationToken, TimeSpan startupDelay)
    {
        var task = Task.Run(() =>
        {
            cancellationToken.ThrowIfCancellationRequested();

            this.acceptor.Start();
            this.IsStarted = true;

            while (true)
            {
                // Stop if token has been canceled
                if (cancellationToken.IsCancellationRequested)
                {
                    this.acceptor.Stop();
                    this.IsStarted = false;

                    cancellationToken.ThrowIfCancellationRequested();
                }

                // Save some CPU cycles
                Thread.Sleep(TimeSpan.FromSeconds(1));
            }

        }, cancellationToken);

        try
        {
            await task;
        }
        catch (OperationCanceledException e)
        {
            Debug.WriteLine(e.Message);
        }
    }
}

以及对应的消费者代码

[Fact]
public void Should_Run_Acceptor_And_Stop_By_CancelationToken()
{
    // Arrange
    var acceptor = new FixAcceptor(new FixAcceptorSettings("test_acceptor.cfg", this.logger));
    var tokenSource = new CancellationTokenSource();

    // Act
    tokenSource.CancelAfter(TimeSpan.FromSeconds(10));
    acceptor.Run(tokenSource.Token, TimeSpan.FromSeconds(3));

    // Assert
    acceptor.IsStarted.Should().BeTrue();
    IsListeningOnTcpPort(9823).Should().BeTrue();

    // Wait for cancel event to occur
    Thread.Sleep(TimeSpan.FromSeconds(15));
    acceptor.IsStarted.Should().BeFalse();
}

添加时间延迟来实现确定性不是推荐的做法。 您可以通过使用TaskCompletionSource来控制任务在合适的时刻完成,从而实现 100% 的确定性:

public Task<bool> Start(CancellationToken cancellationToken)
{
    var startTcs = new TaskCompletionSource<bool>();
    var task = Task.Run(() =>
    {
        cancellationToken.ThrowIfCancellationRequested();

        this.acceptor.Start();
        this.IsStarted = true;
        startTcs.TrySetResult(true); // Signal that the starting phase is completed

        while (true)
        {
            // ...
        }

    }, cancellationToken);
    HandleTaskCompletion();
    return startTcs.Task;

    async void HandleTaskCompletion() // async void method = should never throw
    {
        try
        {
            await task;
        }
        catch (OperationCanceledException ex)
        {
            Debug.WriteLine(ex.Message);
            startTcs.TrySetResult(false); // Signal that start failed
        }
        catch
        {
            startTcs.TrySetResult(false); // Signal that start failed
        }
    }
}

然后在您的测试中替换此行:

acceptor.Run(tokenSource.Token, TimeSpan.FromSeconds(3));

...与这个:

bool startResult = await acceptor.Start(tokenSource.Token);

另一个引起我注意的问题是bool IsStarted属性,它从一个线程发生变异并被另一个线程观察到,没有同步。 这不是一个真正的问题,因为您可以依赖在每个await上自动插入的未记录的内存屏障,并且非常有信心您不会遇到可见性问题,但是如果您想更加确定您可以通过以下方式同步访问使用lock (最健壮),或使用volatile私有字段备份属性,如下所示:

private volatile bool _isStarted;
public bool IsStarted => _isStarted;

我建议你构造你的FixAcceptor.Run()方法有点不同

public async Task Run(CancellationToken cancellationToken, TimeSpan startupDelay)
{
    var task = Task.Run(async () =>
    {
        try 
        {
            cancellationToken.ThrowIfCancellationRequested();

            this.acceptor.Start();
            this.IsStarted = true;

            while (true)
            {
                // Stop if token has been canceled
                if (cancellationToken.IsCancellationRequested)
                {
                    this.acceptor.Stop();
                    this.IsStarted = false;

                    cancellationToken.ThrowIfCancellationRequested();
                }

                // Save some CPU cycles
                await Task.Delay(TimeSpan.FromSeconds(1));
            }
        }
        catch (OperationCanceledException e)
        {
            Debut.WriteLine(e.Message);
        }
    }, cancellationToken);

    await Task.Delay(startupDelay);
}

所以异常处理在内部任务中, Run方法返回一个在startupDelay之后完成的Task (我还用Task.Delay()交换了Thread.Sleep() )然后在测试方法中您可以等待Run返回的Task

[Fact]
public async Task Should_Run_Acceptor_And_Stop_By_CancelationToken()
{
    // Arrange
    var acceptor = new FixAcceptor(new FixAcceptorSettings("test_acceptor.cfg", this.logger));
    var tokenSource = new CancellationTokenSource();

    // Act
    tokenSource.CancelAfter(TimeSpan.FromSeconds(10));
    await acceptor.Run(tokenSource.Token, TimeSpan.FromSeconds(3));

    // Assert
    acceptor.IsStarted.Should().BeTrue();
    IsListeningOnTcpPort(9823).Should().BeTrue();

    // Wait for cancel event to occur
    Thread.Sleep(TimeSpan.FromSeconds(15));
    acceptor.IsStarted.Should().BeFalse();
}

使 mehtode async应该没问题(看起来就像您使用 xunit 一样)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM