链接c#async /等待任务,在完成时重新创建它们

时间:2017-12-07 06:41:29

标签: c# async-await task-parallel-library

真正的美学问题。

鉴于此代码(轮询不可避免):

protected override async Task ExecuteAsync(CancellationToken ct)
{       
    // move the loop here somehow?
    await Task.WhenAll(
        Task.Run(async () => await this.PollA(ct), ct),
        Task.Run(async () => await this.PollB(ct), ct),
        Task.Run(async () => await this.PollC(ct), ct))
        .ConfigureAwait(false);   
}

此时轮询方法看起来像这样,每个方法都有不同的延迟。

private async Task Poll(CancellationToken ct)
{
    while (!ct.IsCancellationRequested)
    {
        await Task.Delay(Math.Max(1000, CONFIGA), ct);
        this._logger.StartAction("poll A status");
        this._logger.StopAction("poll A status");
    }
}

有没有办法在每个Poll方法中构建删除循环的延续

private async Task Poll(CancellationToken ct)
{
    await Task.Delay(Math.Max(1000, CONFIGA), ct);
    this._logger.StartAction("poll A status");
    this._logger.StopAction("poll A status");
}

这可能不是正确的模式,但似乎比有三个无限循环更好。

Task.WhenAny([A,B,C]) => 
// recreate any complete task as soon as it returns 
// and await the new "continuation"?

2 个答案:

答案 0 :(得分:1)

我有一个美学解决方案,这可能不适合使用,因为它可能最终导致堆栈溢出。 它可能会说明为什么循环是一个更好的选择。

我必须承认,在现实世界的背景下,你并不真正理解你的榜样。 在我看来,几乎所有执行很长时间的代码都会在有限循环中执行,因此在每次循环迭代后检查取消对我来说听起来是个好主意。

除非你希望你的代码只是在任务被取消之前无限运行,否则我的美学解决方案可能会导致堆栈溢出(如果留下很长时间),但它很有趣,无论如何都要提供这段代码。

我创建了一个扩展方法:

public static class Extensions
{
    public static async Task ContinueWithInfinitly(this Task task, Func<Task> continuationAction, CancellationToken cancellationToken)
    {
        await task;
        if (!cancellationToken.IsCancellationRequested)
        {
            var newTask = continuationAction.Invoke();
            await newTask.ContinueWithInfinitly(continuationAction, cancellationToken);
        }
    }
}

然后将按如下方式调用您的代码的基础:

await Task.WhenAll(
                Task.Run(async () => await this.PollA(ct).ContinueWithInfinitly(() => PollA(ct), ct)),
                Task.Run(async () => await this.PollB(ct).ContinueWithInfinitly(() => PollB(ct), ct)),
                Task.Run(async () => await this.PollC(ct).ContinueWithInfinitly(() => PollC(ct), ct)))
                .ConfigureAwait(false);

虽然我没有看到在Task.Run中再次包装每个方法的重点。 所以我也可以

await Task.WhenAll(
                this.PollA(ct).ContinueWithInfinitly(() => PollA(ct), ct),
                this.PollB(ct).ContinueWithInfinitly(() => PollB(ct), ct),
                this.PollC(ct).ContinueWithInfinitly(() => PollC(ct), ct))
                .ConfigureAwait(false);

答案 1 :(得分:0)

您可以像这样使用Task.WhenAny

private async Task<Tuple<string, int>> Poll(string type, int delay, CancellationToken ct) {
    await Task.Delay(Math.Max(1000, delay), ct);
    Console.WriteLine($"poll {type} status");
    // return input arguments back
    return Tuple.Create(type, delay);
}

private async Task PollAll(CancellationToken ct) {
    var tasks = new []
    {
        Poll("A", 3000, ct),
        Poll("B", 2000, ct),
        Poll("C", 1000, ct)
    };
    while (!ct.IsCancellationRequested) {
        var completed = await Task.WhenAny(tasks);
        var index = Array.IndexOf(tasks, completed);
        // await to throw exceptions if any
        await completed;                                
        // replace with new task with the same arguments
        tasks[index] = Poll(completed.Result.Item1, completed.Result.Item2, ct);
    }
}