首页 文章

如何在不运行异步方法的情况下排队异步方法的任务结果?

提问于
浏览
2

如果我有一个类,其中包含稍后要执行的任务队列,并且我有 async Task<T> 方法,如何在不执行它的情况下将该异步方法排入队列?

我想“延迟”这个任务,并确保调用者看到它稍后运行,就像在方法体中等待它一样 . ---调用者不应该知道我已经将任务排入队列中 .

现在,如果我的队列已满,我构造并返回一个未运行的新的 Task<T> ,这将返回我的私有异步方法的 .Result

public async Task<T> ExecuteAsync<T>(T transaction) {
    if (mustDelay) {
        Task<T> task = new Task<T>(t => executeAsync((T) t).Result, transaction);
        enqueue(task);
        return await task;
    }
    return await executeAsync(transaction);
}

private async Task<T> executeAsync<T>(T transaction) {
    await someWork();
    return transaction;
}

当其他任务完成时,我将队列任务排队并 Start()

dequeuedTask.Start();

这是否确保调用者看到相同的同步,就像从方法返回等待的结果一样?

2 回答

  • 1

    如何在不运行异步方法的情况下排队异步方法的任务结果?

    简短的回答:你做不到 . 调用 async 方法会执行该方法 . 它必然会开始运行 . 如果您希望能够推迟呼叫,则需要将其包装在可以执行此操作的内容中 .

    一个例子可能是 Func<Task<T>> ,除了您设计与我们分享的一小部分代码表明您希望能够返回一个承诺( Task<T> )以及代表此调用的'll make in the future. You can wrap the whole thing in another task, like in your example code, but IMHO that'一个非常严厉的方法,因为它绑定(并可能创建新的)线程池线程只是为了调用 async 方法 .

    一个更好的方法(恕我直言)来实现这一点是使用 TaskCompletionSource<T> . 您可以将 Func<Task> 存储在队列中,该队列使用返回值来设置 TaskCompletionSource<T> ,然后当您决定可以启动任务时,调用 Func<Task> .

    就像是:

    public Task<T> ExecuteAsync<T>(T transaction) {
        if (mustDelay) {
            TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
    
            enqueue(async () =>
            {
                tcs.SetValue(await executeAsync(transaction));
            });
            return tcs.Task;
        }
        return executeAsync(transaction);
    }
    

    请注意,这里 ExecuteAsync<T>() 不需要 async . 你要么返回 TaskCompletionSource<T> 的任务,要么返回 executeAsync<T>() 方法返回的任务(顺便说一下,有两个名字只在字母大小不同的方法是恕我直言的想法) .

    另请注意,您的队列将存储 Func<Task> 个对象,甚至可能存在 Action 个对象(它通常不赞成 async void 方法,例如上面的匿名方法,但是你没有发现它在这种情况下工作正常) . 当您将项目出列时,您将调用该委托 . 根据您的需要,这将是"fire-and-forget"(如果您存储 Action 委托)或者取消和调用委托的方法可能等待委托的返回值(如果您正在存储 Func<Task> 委托) .

    不幸的是,你的问题相当含糊 . 所以不可能提供比这更多的东西 . 如果您需要其他帮助,请改进问题,以便它包含一个好的Minimal, Complete, and Verifiable code example,清楚地显示您在解决问题时遇到的问题,以及详细描述的适当解释 .

  • -2

    这是一个简化的例子:

    async Task<T> GetResult<T>()
    {
        DoRightAway();
        await DoLater();
        return DoEvenLater();
    }
    
    Task<T> task = GetResult<T>();
    //task is running or complete
    

    您希望通过获取它所代表的不完整任务来推迟 GetResult<T>() 的执行 . 问题是调用 async 方法开始运行它,即使你没有调用 await . 该方法运行到第一个 await ,在此示例中为 await DoLater() .

    要理解为什么在不运行它的情况下无法获得由 async 方法表示的任务,请考虑以下三个概念:

    • Func 对象表示未启动的计算 .

    • 新的 Lazy 对象表示将运行一次的未启动计算 .

    • async 方法的调用表示最终计算 .

    这些概念中的每一个都是截然不同的,但它们可以彼此组合成组合表示 . 我们的 GetResult<T>() 电话是第三个概念的一个例子 . 它将在结果准备好后通过 awaitContinueWith 继续执行 . 在我们的例子中,我们甚至不想开始执行 . 相反,我们需要将第一和第三个概念合并为一个表示 . 也就是说,我们需要一个未启动的计算( Func ),当它最终启动时( async 方法调用),最终将完成 .

    将这种洞察力付诸实践,我们通过将 GetResult<T>() 的身体包裹在_2666216中来解决我们的问题 .

    Func<Task<T>> GetFactory()
    {
        return async () =>
        {
            DoRightAway();
            await DoLater();
            return DoEvenLater();
        }
    }
    

    我们称这个新方法来获取函数:

    Func<Task<T>> function = GetFactory();
    

    恭喜!我们没有运行它就完成了任务!好吧,有点 . 我们有一个包装(尚未创建)任务的函数 . 准备好创建并运行任务时,我们调用该函数:

    Task<T> task = function();
    

    我们确保完成任务完成并得到其结果:

    T result = await task;
    

相关问题