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

How can I queue the Task result of an async method without running it?

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

我想要 "delay" 这个任务,并确保调用者稍后看到它 运行 就像它在方法主体中等待一样。 --- 来电者应该不知道我已将任务排入队列以备后用。

现在,如果我的队列已满,我会在此处构建并 return 新建一个 Task<T>,它不是 运行ning,而是 returning .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();

这是否确保调用者看到与 return 方法中等待的结果相同的同步?

How can I queue the Task result of an async method without running it?

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

一个例子可能是 Func<Task<T>>,除了您屈尊与我们分享的一小段代码表明您希望能够 return 一个承诺 (Task<T>)也代表你将来会打这个电话。您可以将整个事情包装在另一个任务中,就像在您的示例代码中一样,但是恕我直言,这是一种非常笨拙的方法,因为它绑定(并可能创建新的)线程池线程只是为了调用 async 方法.

一个更好的方法(恕我直言)是使用 TaskCompletionSource<T>。您可以在队列中存储一个 Func<Task>,它使用 return 值来设置 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。您要么 return TaskCompletionSource<T> 的任务,要么 return 由 executeAsync<T>() 方法编辑的任务(顺便说一句,有两个方法的名称仅在字母上有所不同-套管是恕我直言 可怕的 想法)。

另请注意,您的队列将存储 Func<Task> 个对象,甚至可能 Action 个对象(它通常不赞成 async void 方法,例如上面的匿名方法,但您没有首先不显示任何异常处理,所以也许你会发现它在这种情况下工作正常)。当您使一个项目出队时,您将调用该委托。根据您的需要,这将是 "fire-and-forget"(如果您存储 Action 委托)或者使委托出列和调用的方法可能等待委托的 return 值(如果您是存储 Func<Task> 个代表)。

很遗憾,您的问题相当含糊。所以不可能提供比这更多的东西。如果您需要其他帮助,请改进问题,使其包含一个很好的 Minimal, Complete, and Verifiable code example ,清楚地显示您正在尝试完成什么以及您具体难以弄清楚什么,以及适当的解释来描述它细节。

这是一个简化的例子:

async Task<T> GetResult<T>()
{
    DoRightAway();
    await DoLater();
    return DoEvenLater();
}

Task<T> task = GetResult<T>();
//task is running or complete

您想通过获取它代表的未完成任务来推迟 GetResult<T>() 的执行。问题是调用 async 方法会启动 运行 调用它,即使您没有await 调用也是如此。方法运行s到第一个await,在这个例子中是await DoLater()

要理解为什么不 运行 就无法获得 async 方法所代表的任务,请考虑以下三个概念:

  • A Func object 代表一个未开始的计算。
  • 一个新的 Lazy object 表示一个未开始的计算,它将 运行 一次。
  • async 方法的调用表示最终计算。

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

将这种见解付诸实践,我们通过将 GetResult<T>() 的 body 包装在 Func.

中解决了我们的问题
Func<Task<T>> GetFactory()
{
    return async () =>
    {
        DoRightAway();
        await DoLater();
        return DoEvenLater();
    }
}

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

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

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

Task<T> task = function();

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

T result = await task;