C#任务忽略取消超时

Pau*_*ney 2 c# task task-parallel-library async-await

我正在尝试为任意代码编写一个包装器,在给定的超时期限后取消(或至少停止等待)代码。

我有以下测试和实现

[Test]
public void Policy_TimeoutExpires_DoStuff_TaskShouldNotContinue()
{
    var cts = new CancellationTokenSource();
    var fakeService = new Mock<IFakeService>();
    IExecutionPolicy policy = new TimeoutPolicy(new ExecutionTimeout(20), new DefaultExecutionPolicy());
    Assert.Throws<TimeoutException>(async () => await policy.ExecuteAsync(() => DoStuff(3000, fakeService.Object), cts.Token));

    fakeService.Verify(f=>f.DoStuff(),Times.Never);
}
Run Code Online (Sandbox Code Playgroud)

和“DoStuff”方法

private static async Task DoStuff(int sleepTime, IFakeService fakeService)
{

    await Task.Delay(sleepTime).ConfigureAwait(false);
    var result = await Task.FromResult("bob");
    var test = result + "test";
    fakeService.DoStuff();
}
Run Code Online (Sandbox Code Playgroud)

以及 IExecutionPolicy.ExecuteAsync 的实现

public async Task ExecuteAsync(Action action, CancellationToken token)
{
    var cts = new CancellationTokenSource();//TODO: resolve ignoring the token we were given!

    var task = _decoratedPolicy.ExecuteAsync(action, cts.Token);
    cts.CancelAfter(_timeout);

    try
    {
        await task.ConfigureAwait(false);
    }
    catch(OperationCanceledException err)
    {
        throw new TimeoutException("The task did not complete within the TimeoutExecutionPolicy window of" + _timeout + "ms", err);
    }
}
Run Code Online (Sandbox Code Playgroud)

什么应该发生的是,测试方法,试图采取> 3000ms和超时应该发生在20毫秒,但这没有发生。为什么我的代码没有按预期超时?

编辑:

根据要求 - 装饰政策如下

public async Task ExecuteAsync(Action action, CancellationToken token)
{
    token.ThrowIfCancellationRequested();
    await Task.Factory.StartNew(action.Invoke, token);  
}
Run Code Online (Sandbox Code Playgroud)

Sri*_*vel 5

如果我理解正确,您正在尝试为不支持超时/取消的方法支持超时。

通常,这是通过启动具有所需超时值的计时器来完成的。如果计时器首先触发,那么您可以抛出异常。使用 TPL,您可以使用Task.Delay(_timeout)而不是计时器。

public async Task ExecuteAsync(Action action, CancellationToken token)
{
    var task = _decoratedPolicy.ExecuteAsync(action, token);

    var completed = await Task.WhenAny(task, Task.Delay(_timeout));
    if (completed != task)
    {
        throw new TimeoutException("The task did not complete within the TimeoutExecutionPolicy window of" + _timeout + "ms");
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:这不会停止_decoratedPolicy.ExecuteAsync方法的执行,而是忽略它。

如果您的方法确实支持取消(但不及时),那么最好在超时后取消任务。您可以通过创建链接令牌来实现。

public async Task ExecuteAsync(Action action, CancellationToken token)
{
    using(var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token))
    {
        var task = _decoratedPolicy.ExecuteAsync(action, linkedTokenSource.Token);

        var completed = await Task.WhenAny(task, Task.Delay(_timeout));
        if (completed != task)
        {
            linkedTokenSource.Cancel();//Try to cancel the method
            throw new TimeoutException("The task did not complete within the TimeoutExecutionPolicy window of" + _timeout + "ms");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)