多线程任务更新 1 个进度条 - UI C# WPF

Multiple threads tasks updating 1 progressbar - UI C# WPF

我一直在四处寻找有类似问题的人,但没有找到任何东西。我正在使用 WPF UI.

编写 C# 应用程序

要更新进度条,我不能像以前那样使用多线程(在 CLI 中),因为它告诉我如果它不是来自主线程,我就不能更新 UI 个元素。

一种解决方案是创建后台工作者。我已经实施了这个解决方案并且效果很好,但是 我希望将任务分配给更多 workers/threads(多线程)以提高效率。

我不知道我要走的方向。如果有人能指导我解决这个问题,那就更受欢迎了。

这是我的代码:(用于使用 MVVM 模式编码,只是在这里粘贴我的代码,它对您来说更简单)

public partial class testFunctionTaskPanel: Page
{
    private BackgroundWorker backgroundWorker1 = new BackgroundWorker();

    private string myURL;

    public testFunctionTaskPanel()
    {
        InitializeComponent();
    }

    private void startButton_Click(object sender, RoutedEventArgs e)
    {
        myURL = myURL.Text;

        myResults.Items.Clear();
        myResults.Items.Add("----Starting----");

        backgroundWorker1.WorkerReportsProgress = true;
        backgroundWorker1.ProgressChanged += ProgressChanged;
        backgroundWorker1.DoWork += DoWork;
        backgroundWorker1.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
        backgroundWorker1.RunWorkerAsync();
    }

    private void DoWork(object sender, DoWorkEventArgs e)
    {
        int length = myLoadedList.Items.Count;


        for (int i = 1; i <= length; i++)
        {
            try
            {
                HttpRequest req = new HttpRequest();
                req.Proxy = null;
                req.ConnectTimeout = 5000;
                req.IgnoreProtocolErrors = true;

                string get = myURL + myLoadedList.Items[i].ToString();
                var response = req.Get(get);

                if (response.StatusCode == Leaf.xNet.HttpStatusCode.OK)
                {
                    this.Dispatcher.Invoke(() =>
                    {
                        myResults.Items.Add(myLoadedList.Items[i].ToString());
                    });
                }
            }
            catch{}
            backgroundWorker1.ReportProgress(i);
        }
    }

    private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        myResults.Items.Add("----Finish----");
    }

    private void ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        // This is called on the UI thread when ReportProgress method is called
        progressbar.Value = e.ProgressPercentage;
    }
}

您可以使用 Dispatcher.Invoke():

Dispatcher.Invoke(() =>
{
    // Your code here
});

当您在线程或任务中时,只需调用它并将 UI 更新代码粘贴到其中。

我的首选方法是:

  1. 每个 task/thread 更新一个单独的进度值
  2. 主线程上的计时器 运行 每 X 毫秒计算一次所有进度值的平均值并更新进度条绑定到的 属性。

这种方法的一个优点是可以在紧密循环中更新值,而不会冒调用请求淹没消息队列的风险。

您可以使用 Parallel class or the PLINQ library to process the items in parallel, using multiple ThreadPool threads. For reporting the progress to the UI you could use the IProgress<T> abstraction, where T can be any type of your choice. For example it could be a ValueTuple<string, bool> 来传达已处理的项目,也可以使用 success/failure 操作。这样您就可以创建一个与应用程序无关的、类库的方法。您可以将此方法逐字复制粘贴到完全不同的应用程序(例如控制台应用程序)中,无需任何修改,它的工作方式将完全相同。下面是这种方法的示例,它使用 PLINQ 库来处理并行性:

public static string[] ProcessAllItems(string[] items, string baseUrl,
    IProgress<(string, bool)> progress)
{
    return items
        .AsParallel()
        .AsOrdered()
        .WithDegreeOfParallelism(4)
        .Select(item =>
        {
            HttpRequest req = new HttpRequest();
            req.Proxy = null;
            req.ConnectTimeout = 5000;
            req.IgnoreProtocolErrors = true;
            var response = req.Get(baseUrl + url);
            if (response.StatusCode == Leaf.xNet.HttpStatusCode.OK)
            {
                progress.Report((item, true)); // Success
                return item;
            }
            progress.Report((item, false)); // Failure
            return null;
        })
        .Where(result => result != null)
        .ToArray();
}

然后您所要做的就是在 UI 线程上创建一个 Progress<(string, bool)> 对象,并传递一个处理来自后台线程的报告消息的委托。此委托应同时更新 myResultsprogressbar UI 元素。调用 ProcessAllItems 应包含在 await Task.Run 中,以保持 UI 响应。

private async void startButton_Click(object sender, RoutedEventArgs e)
{
    string baseUrl = myURL.Text;
    string[] items = myLoadedList.Items.Select(x => x.ToString()).ToArray();
    var completedCount = 0;

    var progress = new Progress<(string, bool)>(message =>
    {
        if (message.Item2)
        {
            myResults.Items.Add(message.Item1);
        }
        completedCount++;
        progressbar.Value = completedCount * 100 / items.Length;
    });

    progressbar.Value = 0;
    myResults.Items.Clear();
    myResults.Items.Add("----Starting----");

    string[] results = await Task.Run(() =>
    {
        return ProcessAllItems(items, baseUrl, progress);
    });

    progressbar.Value = 100;
    myResults.Items.Add("----Finish----");
}

注意 async keyword in the startButton_Click handler, that enables the use of the await 运算符。

此建议的要点是避免使用笨拙的 Dispatcher.Invoke 方法,该方法鼓励将处理逻辑与表示逻辑以及 technologically obsolete BackgroundWorker class.