ContinueWith(委托的CancellationToken)与等待延续等效ContinueWith、CancellationToken

2023-09-04 01:54:10 作者:顾念一人

我有这种情况:

private Task LongRunningTask = /* Something */;

private void DoSomethingMore(Task previousTask) { }

public Task IndependentlyCancelableSuccessorTask(CancellationToken cancellationToken)
{
    return LongRunningTask.ContinueWith(DoSomethingMore, cancellationToken);
}

在特定的,这里吸引我的行为是 MSDN的有关延续任务页的详细在以下方面:

In particular, the behavior that interests me here is detailed in MSDN's page about Continuation Tasks in the following terms:

一个延续进入取消状态,在这些情况下:

A continuation goes into the Canceled state in these scenarios:    [...]   在当前的延续是通过了System.Threading.CancellationToken作为参数和IsCancellationRequested令牌的属性是之前的延续运行。在这样的情况下,继续维持不启动,并将其转换到Canceled状态。    [...] When the continuation was passed a System.Threading.CancellationToken as an argument and the IsCancellationRequested property of the token is true before the continuation runs. In such a case, the continuation does not start and it transitions to the Canceled state.

在code以上的作品。然而,我在把尽可能多的我延续使用等待关键字的过程。

The code above works. However, I am in the process of converting as many as possible of my continuations to using the await keyword.

有没有使用等效等待,将允许继续下去,从而被取消了期待已久的任务完成之前?

Is there an equivalent using await that would allow the continuation to be canceled before the awaited task completes?

推荐答案

下面应该这样做,尽管它看起来有点尴尬:

The following should do it, albeit it looks a bit awkward:

private Task LongRunningTask = /* Something */;

private void DoSomethingMore() { }

public async Task IndependentlyCancelableSuccessorTask(
    CancellationToken cancellationToken)
{
    cancellationToken.ThrowIfCancellationRequested();

    var tcs = new TaskCompletionSource<bool>();
    using (cancellationToken.Register(() => tcs.TrySetCanceled()))
        await Task.WhenAny(LongRunningTask, tcs.Task);

    cancellationToken.ThrowIfCancellationRequested();
    DoSomethingMore();
}

[更新] 以下svick的建议,在这里它的形状为一个帮手,根据斯蒂芬Toub的Implementing然后等待模式:

[UPDATE] Following svick's suggestion, here it is shaped as a helper, based on Stephen Toub's Implementing Then with Await pattern:

public static class TaskExt
{
    /// <summary>
    /// Use: await LongRunningTask.Then(DoSomethingMore, cancellationToken)
    /// </summary>
    public static async Task Then(
        this Task antecedent, Action continuation, CancellationToken token)
    {
        await antecedent.When(token);
        continuation();
    }

    /// <summary>
    /// Use: await LongRunningTask.When(cancellationToken)
    /// </summary>
    public static async Task When(
        this Task antecedent, CancellationToken token)
    {
        token.ThrowIfCancellationRequested();

        var tcs = new TaskCompletionSource<Empty>();
        using (token.Register(() => tcs.TrySetCanceled()))
            await Task.WhenAny(antecedent, tcs.Task);

        token.ThrowIfCancellationRequested();
    }

    struct Empty { };
}

也许,第一个 ThrowIfCancellationRequested()是多余的,但我还没有仔细考虑过所有的边缘情况。

Perhaps, the first ThrowIfCancellationRequested() is redundant, but I haven't thoroughly considered all edge cases.