使用 CancelAfter 时如何在任务取消后避免 SetResult

How to avoid SetResult after task cancellation when using CancelAfter

我将 EAP 转换为 TAP。但是,处理的事件可能根本不会被触发,这就是我添加超时的原因。查看示例代码

class Request<T> {
    TaskCompletionSource<T> tcs;
    CancellationTokenSource cts;
    public int Timeout { get; }
    public bool IsCanceled => tcs.Task.IsCanceled;

    public Request(int timeout = System.Threading.Timeout.Infinite) => Timeout = timeout;

    public Task<T> StartRequestAsync() {
        cts.Token.Register(() => tcs.SetCanceled());
        cts.CancelAfter(Timeout);
        return tcs.Task;
    }

    public void OnEvent(T result) {
        tcs.SetResult(result);
    }
}

请注意,在当前 class 的 IDisposable 的实施中,CancellationTokenSourceCancellationTokenRegistration 均已正确处理。 StartRequestAsync 方法也被线程安全地阻止 运行 不止一次。为简洁起见简化了代码。

现在,我正在外部设置任务的结果。

var req = new Request<int>();
if(!req.IsCanceled) 
{
    req.SetResult(5);
}

但我认为存在竞争条件。我假设取消标记 CancelAfter 用作中断。如果令牌在执行进入 if 块后被取消,我们可以得到 InvalidOperationException: An attempt was made to transition a task to a final state when it had already completed.

文档指出 TaskCompletionSource 是线程安全的,那么这是否意味着 TrySetResult 是我在这里尝试实现的原子版本?那是我应该改用的吗?

还有其他方法可以解决吗?

了解 SetResult and SetCanceled 方法的实现方式可能会有所帮助:

public void SetResult(TResult result)
{
    if (!TrySetResult(result))
        throw new InvalidOperationException(
            Environment.GetResourceString("TaskT_TransitionToFinal_AlreadyCompleted"));
}

public void SetCanceled()
{
    if(!TrySetCanceled())
        throw new InvalidOperationException(
            Environment.GetResourceString("TaskT_TransitionToFinal_AlreadyCompleted"));
}

如您所见,这两种方法都委托给它们的 Try 对应方。如果您的场景是确定性的,您需要它们来检测代码中的错误。如果不是,并且竞争条件是场景中固有的,请使用 Try 版本。