【问题标题】:How can I implement a lazy TaskCompletionSource?我怎样才能实现一个懒惰的TaskCompletionSource?
【发布时间】:2016-10-30 04:57:36
【问题描述】:

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

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

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

我相信答案可能在于一个自定义 TaskAwaiter,正如 Scott Chamberlain 在 Implement AsyncManualResetEvent using Lazy<T> to determine if the task has been awaited 中所描述的那样,但我不能完全从他的例子中得到我的解决方案...... :(

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;
}

【问题讨论】:

    标签: c# multithreading async-await task-parallel-library lazy-evaluation


    【解决方案1】:

    正如 usr 所说,不可能对 Task 成为 awaited 做出反应。但是,如果您可以使用自定义的可等待对象,那么您可以。

    一个简单的方法是使用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));
    

    【讨论】:

    【解决方案2】:

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

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

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

    这将返回一个任务工厂而不是一个任务。这是作弊,但唯一可能的解决方案涉及这种作弊。

    您也可以返回一个自定义的等待对象。但这根本不涉及任务,它错过了任务的可组合性特征。 Awaitables 主要是一个 C# 概念。暴露它们可能会导致 API 不干净。


    与您的问题无关:您可以删除 await tcs.Task 并直接返回该任务。此外,不需要result 函数。返回没有结果的Task。然后,调用者可以根据需要添加结果。这使得WaitOneAsync 的API 更加简洁。

    【讨论】:

    • Usr,很高兴知道 TPL 没有提供开箱即用的功能,所以我没有遗漏一些明显的东西。有没有办法扩展(又名“hack”)TPL 以添加此功能?在我看来,Scott 正在使用 ICriticalNotifyCompletion.[Unsafe]OnCompleted 来拦截延续附加事件。如果是这样,那么我希望对这些未记录的 TPL 管道 API 有一定经验的人会认为这是一个有价值的一般场景并能够提出解决方案。..
    • 他所做的事情有完整的文档记录并且在概念上并不困难。他做了习惯等待的事情。这不再是一项任务。您只能等待这个东西,但不能将它与任务 API 一起使用。
    • 啊。那么这就是为什么我无法理解如何将他的工作变成一项任务!我会按照你的建议去做,除了我将使用Lazy&lt;Task&lt;T&gt;&gt; 来卸载确保只创建一个 TaskCompletionSource 的工作。谢谢!
    猜你喜欢
    • 2021-10-01
    • 2020-02-07
    • 1970-01-01
    • 2015-03-21
    • 2019-11-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-02
    相关资源
    最近更新 更多