多线程任务更新 1 个进度条 - UI C# WPF
Posted
技术标签:
【中文标题】多线程任务更新 1 个进度条 - UI C# WPF【英文标题】:Multiple threads tasks updating 1 progressbar - UI C# WPF 【发布时间】:2021-07-16 19:30:51 【问题描述】:我一直在四处寻找有类似问题的人,但没有找到任何东西。我正在使用 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;
【问题讨论】:
如果您使用任务和调用,您可能不需要BackgroundWorker
。请参阅docs.microsoft.com/en-us/dotnet/api/… Invoke 将阻止您在尝试从另一个线程更新进度条时看到的错误消息。
这个话题有很好的处理here。
BackgroundWorker 已弃用。在现代 Sharp 中,使用异步方法(async/await)和 Task 是正确的。
大部分问题都是你自己制造的。 WPF 中的常见做法是使用数据绑定而不是直接访问 UI 元素。使用绑定时,可以在任何线程中更改数据。
@RobertHarvey 非常感谢您的帮助。我想,我尝试了很多解决方案,但迷失了自己。果然,答案就在眼前。再次感谢您的帮助
【参考方案1】:
你可以使用Dispatcher.Invoke()
:
Dispatcher.Invoke(() =>
// Your code here
);
当您在线程或任务中时,只需调用它并将 UI 更新代码粘贴到其中。
【讨论】:
非常感谢您的帮助。我确实是“盲人”。我想,我尝试了很多解决方案,但迷失了自己。果然,答案就在眼前。再次感谢您的帮助。 @VicoLays 确保您的调度程序不为空,因为使用这种方法您的测试将失败。 请注意,Dispatcher.Invoke
“同步地执行指定的委托”。 IE。工作线程将阻塞,直到 UI 更新。【参考方案2】:
您可以使用Parallel
类或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)>
对象,并传递一个处理来自后台线程的报告消息的委托。此委托应更新 myResults
和 progressbar
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
方法,该方法鼓励将处理逻辑与表示逻辑以及technologically obsolete BackgroundWorker
类混合在一起。
【讨论】:
非常感谢您的出色帮助。你的解决方案真的很棒。我会尽快申请。【参考方案3】:我的首选方法是:
-
每个任务/线程更新一个单独的进度值
在主线程上运行的计时器平均每 X 毫秒的所有进度值,并更新进度条绑定到的属性。
这种方法的一个优点是可以在紧密的循环中更新值,而不会冒着调用请求淹没消息队列的风险。
【讨论】:
如果你使用了错误的计时器并且不释放事件处理程序,这也会带来内存泄漏的风险。 @XAMIMAX 好吧,是的,您需要正确使用这些工具。我认为这不需要提及。以上是关于多线程任务更新 1 个进度条 - UI C# WPF的主要内容,如果未能解决你的问题,请参考以下文章
android:异步任务asyncTask介绍及异步任务下载图片(带进度条)