多线程任务更新1 progressbar-UI C#WPF

多线程任务更新1 progressbar-UI C#WPF,c#,wpf,multithreading,user-interface,backgroundworker,C#,Wpf,Multithreading,User Interface,Backgroundworker,我一直在四处寻找有类似问题的人,但什么也没找到。我正在使用WPF UI编写一个C#应用程序 要更新progressbar,我不能像以前那样(在CLI中)使用多线程,因为它告诉我,如果UI元素不是来自主线程,我就不能更新UI元素 一种解决方案是创建后台工作人员。我已经实现了这个解决方案,而且效果很好,但是我希望将任务分配给更多的工作线程(多线程),以提高效率 我不知道我要走的方向。如果有人能向我介绍这个问题,我会更加欢迎 这是我的代码:(用于使用MVVM模式进行编码,只是在这里粘贴我的代码,这对您

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

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

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

我不知道我要走的方向。如果有人能向我介绍这个问题,我会更加欢迎

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

公共部分类testFunctionTaskPanel:第页
{
私有BackgroundWorker backgroundWorker1=新BackgroundWorker();
私有字符串myURL;
公共testFunctionTaskPanel()
{
初始化组件();
}
私有无效开始按钮单击(对象发送者,路由目标)
{
myURL=myURL.Text;
myResults.Items.Clear();
myResults.Items.Add(“----开始----”;
backgroundWorker1.WorkerReportsProgress=true;
backgroundWorker1.ProgressChanged+=ProgressChanged;
backgroundWorker1.dowwork+=dowwork;
backgroundWorker1.RunWorkerCompleted+=BackgroundWorker\u RunWorkerCompleted;
backgroundWorker1.RunWorkerAsync();
}
私有无效DoWork(对象发送方,DoWorkEventArgs e)
{
int length=myLoadedList.Items.Count;
对于(int i=1;i
{
myResults.Items.Add(myLoadedList.Items[i].ToString());
});
}
}
捕获{}
背景工作1.报告进度(一);
}
}
私有void BackgroundWorker_RunWorkerCompleted(对象发送方,runworkercompletedeventarge)
{
myResults.Items.Add(“--Finish-->”);
}
私有void ProgressChanged(对象发送方,progresschangedventargs e)
{
//当调用ReportProgress方法时,将在UI线程上调用它
progressbar.Value=e.ProgressPercentage;
}
}

您可以使用
Dispatcher.Invoke()


当您处于线程或任务中时,只需调用此函数并将UI更新代码粘贴到其中。

我首选的方法是:

  • 每个任务/线程更新一个单独的进度值
  • 在主线程上运行的计时器每隔X毫秒计算所有进度值的平均值,并更新进度条绑定到的属性
  • 这种方法的一个优点是,可以在紧循环中更新值,而不会有调用请求淹没消息队列的风险。

    您可以使用类或库,使用多个
    线程池
    线程并行处理项。为了向UI报告进度,您可以使用抽象,其中
    T
    可以是您选择的任何类型。例如,它可以是一个,以便传达处理的项目和操作的成功/失败。通过这种方式,您可以创建一个与应用程序无关的类库方法。您可以在一个完全不同的应用程序(例如控制台应用程序)中逐字复制粘贴此方法,它的工作原理完全相同,无需任何修改。下面是使用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();
    }
    
    请注意
    startButton\u Click
    处理程序中的关键字,它启用了运算符的使用


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

    如果使用任务和调用,则可能不需要
    BackgroundWorker
    。See Invoke将阻止您在尝试从另一个线程更新进度条时看到的错误消息。此主题处理得很好。BackgroundWorker已被弃用。在现代夏普中,使用异步方法(async/await)和任务是正确的。大多数问题都是您自己造成的。WPF中的常见做法是使用数据绑定,而不是直接访问UI元素。使用绑定时,数据可以在任何线程中更改。@RobertHarvey非常感谢您的帮助。我想,我尝试了很多解决方案,但还是迷失了自我。事实上,答案就在我面前。再次非常感谢你的帮助。我确实是“瞎子”。我想,我尝试了很多解决方案,但还是迷失了方向。事实上,答案就在我面前。再次感谢您的帮助。@VicoLays确保您的调度程序不为null,因为您的测试将因此方法而失败。请注意,
    dispatcher.Invoke
    “同步执行指定的委托”。也就是说,工作线程将被阻塞,直到UI被更新。如果使用错误的计时器并且不处理事件处理程序,这也会带来内存泄漏的风险。@XAMIMAX是的,您需要正确使用这些工具。我不认为这需要提及。非常感谢你的帮助。你的解决方案真的很棒。我会尽快申请。
    Dispatcher.Invoke(() =>
    {
        // Your code here
    });
    
    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();
    }
    
    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----");
    }