等待超时的任务

本文关键字:任务 超时 等待 | 更新日期: 2023-09-27 18:36:23

我正在尝试编写一个帮助程序方法,该方法允许我传入任意任务和超时。如果任务在超时之前完成,则调用成功委托,否则调用错误委托。该方法如下所示:

    public static async Task AwaitWithTimeout(Task task, int timeout, Action success, Action error)
    {
        if (await Task.WhenAny(task, Task.Delay(timeout)) == task)
        {
            if (success != null)
            {
                success();
            }
        }
        else
        {
            if (error != null)
            {
                error();
            }
        }
    }

现在这似乎大部分时间都有效,但我也想编写一些测试来确保。令我惊讶的是,这个测试失败了,并调用了错误委托而不是成功:

        var taskToAwait = Task.Delay(1);
        var successCalled = false;
        await TaskHelper.AwaitWithTimeout(taskToAwait, 10, () => successCalled = true, null);
        Assert.IsTrue(successCalled);

但是,此测试是绿色的:

        var taskToAwait = Task.Run(async () =>
        {
            await Task.Delay(1);
        });
        var successCalled = false;
        await TaskHelper.AwaitWithTimeout(taskToAwait, 10, () => successCalled = true, null);
        Assert.IsTrue(successCalled);

如何使两个测试都绿色?我对 Task.WhenAny 的使用是否不正确?

等待超时的任务

计时器不准确。默认情况下,它们的精度约为 15 毫秒。低于此值的任何值都将在 15 毫秒的间隔内触发。参考相关答案。

假设你有1ms的计时器

和10ms的计时器;两者都大致相等,所以你会得到不一致的结果。

您包装在Task.Run中并声称正在运行的代码只是一个巧合。当我尝试几次时,结果不一致。由于上述相同原因,它有时会失败。

您最好增加超时时间,或者只是传递已完成的任务。

例如,以下测试应始终通过。 请记住,您的测试应该是一致的,而不是脆弱的。

[Test]
public async Task AwaitWithTimeout_Calls_SuccessDelegate_On_Success()
{
    var taskToAwait = Task.FromResult(0);
    var successCalled = false;
    await TaskHelper.AwaitWithTimeout(taskToAwait, 10, () => successCalled = true, ()=>{ });
    Assert.IsTrue(successCalled);
}

对于永无止境的任务,请使用TaskCompletionSource,不要设置其结果。

[Test]
public async Task AwaitWithTimeout_Calls_ErrorDelegate_On_NeverEndingTask()
{
    var taskToAwait = new TaskCompletionSource<object>().Task;
    var errorCalled = false;
    await TaskHelper.AwaitWithTimeout(taskToAwait, 10, () => { }, ()=> errorCalled = true);
    Assert.IsTrue(errorCalled);
}

另外,我建议您避免使用null。您可以只将空委托作为参数传递。然后,您不希望空检查分散在整个代码库中。

我将帮助程序方法编写为:

public static async Task AwaitWithTimeout(this Task task, int timeout, Action success, Action error)
{
    if (await Task.WhenAny(task, Task.Delay(timeout)) == task)
    {
        success();
    }
    else
    {
        error();
    }
}

请注意,上面的方法是扩展方法;因此您可以使用任务实例调用它。

await taskToAwait.AwaitWithTimeout(10, () => { }, ()=> errorCalled = true);//No nulls, just empty delegate