如何获取具有多个任务的异步函数的进度报告,其中我使用了await Task.WhenAll(tasks);

问题描述 投票:0回答:1

我已经阅读了所有这些帖子:

报告异步任务的进度

https://devblogs.microsoft.com/dotnet/async-in-4-5-enabling-progress-and-cancellation-in-async-apis/

https://blog.stephencleary.com/2012/02/reporting-progress-from-async-tasks.html

但我仍然不知道如何在使用时报告许多任务的进度

await Task.WhenAll(tasks);

所以,我正在使用这段代码:

public string StatusMessage { get; set; }
public async void ButtonClickHandler(object sender, EventArgs e)
{
    var tasks = new List<Task>
    {
        Task1,
        Task2,
        Task3,
        Task4,
        Task5
    };
    await Task.WhenAll(tasks);
}

因此,当

StatusMessage
上的任何内容完成时,我想对
Task1..Task5
进行更改。不知何故,每个任务都必须有一个名称,并且该名称要报告给
StatusMessage
变量。

我还希望当

StatusMessage
变化时发生一个事件。

StatusMessage
也可以是一个累积列表或几乎是一个字符串。

JSteward 的帖子来自here,这很有趣,但我不知道如何从他的示例中将我的 Task1..Task5 任务转换为

WorkItem

我该怎么做?

c# asynchronous async-await maui progress
1个回答
1
投票

如果您想做更详细的进度报告,标准

IProgress<T>
有点限制。没有简单的方法来包含描述等功能或将工作分配给多个独立任务。

我的偏好是用自定义进度类替换它,例如

public class Progress
{
    // object should *either* have value+description, *or* subProgress list
    private double value;
    private string description = "";
    private Progress[] subProgress;

    // progress should always be between 0-1 to keep math simple
    public double Value
    {
        get
        {
            var subProgressLocal = subProgress;
            if (subProgress != null)
            {
                return subProgressLocal.Sum(s => s.Value ) / subProgressLocal.Length;
            }

            return value;
        }
    }

    public string Description
    {
        get
        {
            var subProgressLocal = subProgress;
            if (subProgressLocal != null)
            {
                return subProgressLocal.FirstOrDefault(p => p.Value is > 0d and < 1d)?.Description ?? "";
            }

            return description;
        }
    }

    public Progress[] CreateSubProgress(int count)
    {
        if (subProgress != null)
            throw new InvalidOperationException("Can only create sub-progresses once");
        subProgress = Enumerable.Range(0, count).Select(_ => new Progress()).ToArray();
        return subProgress;
    }

    // progress should always be between 0-1 to keep math simple
    public void Report(double progress, string description = "")
    {
        if (subProgress != null)
            throw new InvalidOperationException("Cannot report progress when using sub-progress");
        // Clamp to 0-1 range
        this.value = progress < 0 ? 0 : (progress > 1 ? 1 : progress);
        this.description = description;
    }
}

这允许您构建与您正在执行的计算相匹配的“树”,其中所有报告都在叶节点上完成。因此,您可以拥有多个嵌套循环,同时仍然获得详细的进度报告,并且无需在开始之前知道工作总量。

当任务报告进度时,这不会在 UI 线程上引发任何事件。因此,您需要使用 UI 兼容的计时器来定期轮询进度并更新 UI。另请注意,这不是严格的线程安全,因为将从不同的线程读取和写入共享变量,但可能发生的最坏情况应该是进度条稍微滞后。另请注意,这是一个“一次性使用”对象,每次需要进度报告时都创建一个新对象。

用法如下:

void DoMultiStepWork(Progress p){
    var subprogress = p.CreateSubProgress(3);
    DoPart1(subProgress[0]);
    DoPart2(subProgress[1]);
    DoPart3(subProgress[2]);
}
void DoPart1(Progress p){
    for(int i = 0; i < myCollection.Count; i++){
       // do work
       p.Report(i / (double)myCollection.Count, myCollection[i].Name);
    }
}

您可以编写扩展方法来使使用更方便,例如

foreach(var item in myCollection.ProgressReporting(p))
。或者为每个子进度添加权重等功能,以处理工作分配不均匀的情况。

即使每个方法在不同的任务或线程上运行,这也应该有效。

© www.soinside.com 2019 - 2024. All rights reserved.