如何实现惰性TaskCompletionSource?

时间:2016-10-29 16:36:02

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

如果我的TaskCompletionSource公开的Task可能永远不会被调用,那么除非有人在等待任务,否则我怎么能不计算结果的计算呢?

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

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

我相信答案可能在于Scott Chamberlain所描述的自定义TaskAwaiter 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;
}

2 个答案:

答案 0 :(得分:4)

正如我们所说,对Task await 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)); 进行反应是不可能的。但是,如果你可以使用自定义等待,那么你可以。

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

public static boolean contains(final int[] array, final int key)
{
return Arrays.stream(array).anyMatch(n->n==key);
}

答案 1 :(得分:1)

这与您描述的要求完全不同。当有人添加延续或等待任务时,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更加清晰。