如何实现惰性 TaskCompletionSource?

How can I implement a lazy TaskCompletionSource?

如果我的 TaskCompletionSource 公开的任务可能永远不会被调用,我该如何推迟结果的计算,除非并且直到有人等待该任务?

例如,我想阻止其他异步执行线程,直到使用以下函数 WaitOneAsync 发出 ManualResetEvent 信号。我在 ThreadPool.RegisterWaitForSingleObject 的回调中完成了 TaskCompleationSource,这在 WaitHandle 发出信号时发生。但是,如果没有人等待任务,那么我不想注册 WaitForSingleObject(如果在发出 WaitHandle 信号后等待任务,我也不想注册 WaitForSingleObject)。

我如何更改 WaitOneAsync,以便计算结果的工作,到 RegisterWaitForSingleObject,仅在有人等待 TaskCompleationSource.Task 之后发生?

我相信答案可能在于 Scott Chamberlain 在此处 中描述的自定义 TaskAwaiter,但我无法从他的示例中完全理解我的解决方案...:(

public static async Task<T> WaitOneAsync<T>(this WaitHandle waitHandle, Func<T> result) {

    var tcs = new TaskCompletionSource<T>();

    RegisteredWaitHandle rwh = null;
    rwh = ThreadPool.RegisterWaitForSingleObject(
        waitObject: waitHandle,
        callBack: (s, t) => {
            rwh.Unregister(null);
            tcs.TrySetResult(result());
        },
        state: null,
        millisecondsTimeOutInterval: -1,
        executeOnlyOnce: true
    );

    return await tcs.Task;
}

这不可能完全按照您描述的要求进行。当有人添加延续或等待任务时,TPL 不提供事件或回调。

因此您需要构建 API 以便仅实际生成需要的任务。这个呢?

public static Func<Task<T>> CreateWaitOneAsyncFactory<T>(this WaitHandle waitHandle, Func<T> result) {
 return () => WaitOneAsync(waitHandle, result);
}

这 return 是任务工厂而不是任务。这是作弊,但唯一可能的解决方案是这种作弊。

您也可以 return 自定义等待。但这根本不涉及任务,而且它忽略了任务的可组合性特征。 Awaitables 主要是 C# 概念。暴露它们会导致不干净 APIs.


与您的问题无关:您可以直接删除 await tcs.Task 和 return 该任务。此外,不需要 result 函数。 Return 一个 Task 没有结果。然后,呼叫者可以根据需要添加结果。这使得 WaitOneAsync 的 API 更干净。

正如 usr 所说,不可能对 Taskawait 编辑做出反应。但是,如果您可以使用自定义 awaitable,那么您可以。

一个简单的方法是使用 AsyncLazy from Stephen Cleary's AsyncEx:

private static Task<T> WaitOneAsyncImpl<T>(WaitHandle waitHandle, Func<T> result)
{
    if (waitHandle.WaitOne(0))
        return Task.FromResult(result());

    var tcs = new TaskCompletionSource<T>();

    RegisteredWaitHandle rwh = null;
    rwh = ThreadPool.RegisterWaitForSingleObject(
        waitObject: waitHandle,
        callBack: (s, t) =>
        {
            rwh.Unregister(null);
            tcs.TrySetResult(result());
        },
        state: null,
        millisecondsTimeOutInterval: -1,
        executeOnlyOnce: true
    );

    return tcs.Task;
}

public static AsyncLazy<T> WaitOneAsync<T>(this WaitHandle waitHandle, Func<T> result)
    => new AsyncLazy<T>(() => WaitOneAsyncImpl(waitHandle, result));