繁体   English   中英

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

[英]Multiple threads tasks updating 1 progressbar - UI C# WPF

我一直在四处寻找有类似问题的人,但没有找到任何东西。 我正在使用 WPF UI 编写 C# 应用程序。

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

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

我不知道我必须采取的方向。 如果有人可以指导我解决这个问题,那将更受欢迎。

这是我的代码:(用于使用 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. 每个任务/线程更新一个单独的进度值
  2. 在主线程上运行的计时器平均每 X 毫秒的所有进度值,并更新进度条绑定到的属性。

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

您可以使用Parallel class 或PLINQ库来并行处理项目,使用多个ThreadPool线程。 为了向 UI 报告进度,您可以使用IProgress<T>抽象,其中T可以是您选择的任何类型。 例如,它可以是ValueTuple<string, bool> ,以便传达已处理的项目以及操作的成功/失败。 通过这种方式,您可以创建一个与应用程序无关的类库方法。 您可以在完全不同的应用程序(例如控制台应用程序)中逐字复制粘贴此方法,并且无需任何修改即可完全一样地工作。 下面是这种方法的一个示例,它使用 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)> object,并传递一个处理来自后台线程的报告消息的委托。 此委托应更新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----");
}

注意startButton_Click处理程序中的async关键字,它允许使用await运算符。

这个建议的要点是避免使用笨拙的Dispatcher.Invoke方法,该方法鼓励将处理逻辑与表示逻辑混合,以及技术上已经过时BackgroundWorker class。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM