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

问题描述 投票:0回答:2

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

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

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

Thread.Sleep(startupDelay)this.acceptor.Start()之间放置一个额外的this.IsStarted = true将无济于事,因为它只会阻止辅助任务本身,而不会阻止调用线程。

我希望很清楚我要存档的内容以及我正在努力解决的问题。预先感谢。

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();
}
c# async-await task-parallel-library
2个回答
1
投票

我建议您将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方法返回Task,该startupDelayThread.Sleep()之后完成。(我也用Task.Delay()交换了Task)然后,在测试方法中,您可以等待Run返回的[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(); }

async

制作Mehtode TaskCompletionSource(像使用xunit一样接缝)应该没关系


0
投票

不建议增加时间延迟来确定性。通过使用TaskCompletionSource在适当的时候控制任务的完成,您可以实现100%的确定性:

public Task 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);
        }
        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;
© www.soinside.com 2019 - 2024. All rights reserved.