(C#) BackgroundWorker() ProgressChanged 不工作
(C#) BackgroundWorker() ProgressChanged not working
我有一个 WPF 应用程序,它由两个线程组成,模拟企业在 52 周内生产和销售商品(每周只允许一次交易)。我还需要使用后台工作者,以便我可以在列表视图中显示数据。截至目前,我的 UI 在单击 simulate
时冻结,但我可以看到输出仍在调试终端中运行。我已经尝试了所有我能想到的方法,老实说,我得到了我老师的帮助,甚至他也找不到可行的解决方案。
- 当我调用
Simulate()
时,是什么冻结了我的 UI?
- 当我的代码不同并且我的 UI 没有冻结时,我的列表视图永远不会更新,因为它似乎
DataProgress()
不起作用 — e.UserStart
永远不会迭代。
模拟按钮调用:
private void Simulate(object sender, RoutedEventArgs e)
{
// Declare BackgroundWorker
Data = new ObservableCollection<Operations>();
worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.RunWorkerAsync(52);
worker.DoWork += ShowData;
worker.ProgressChanged += DataProgress;
worker.RunWorkerCompleted += DataToDB;
Production = new Production(qtyProduction, timeExecProd);
Sales = new Sales(qtySales, timeExecSales);
Thread prod = new Thread(Production.Product);
prod.Start();
Thread.Sleep(100);
Thread sales = new Thread(Sales.Sell);
sales.Start();
}
DoWork : ShowData() :
Console.WriteLine("Simulation started | Initial stock : 500");
Production = new Production(qtyProduction, timeExecProd);
Sales = new Sales(qtySales, timeExecSales);
while (Factory.Week < max) // max = 52
{
if (worker.CancellationPending) // also this isn't reacting to worker.CancelAsync();
e.Cancel = true;
// My teacher tried to call my threads from here, but it breaks the purpose of having
// two threads as he was just calling 52 times two functions back to back and therefore
// wasn't "randomizing" the transactions.
int progressPercentage = Convert.ToInt32(((double)(Factory.Week) / max) * 100);
(sender as BackgroundWorker).ReportProgress(progressPercentage, Factory.Week);
}
ProgressChanged : DataProgress() :
if (e.UserState != null) // While using debugger, it looks like this is called over & over
{
Data.Add(new Operations()
{
id = rnd.Next(1,999),
name = Factory.name,
qtyStock = Factory.Stock,
averageStock = Factory.AverageStock,
week = Factory.Week
});
listview.ItemsSource = Data;
}
RunWorkerCompleted : DataToDB() :
// Outputs "Work done" for now.
如果您想知道当我调用我的线程时会发生什么,它看起来像这样:
卖出() :
while (Factory.Week <= 52)
{
lock (obj)
{
// some math function callings¸
Factory.Week++;
}
Thread.Sleep(timeExecSales);
}
我应该使用第三个线程来更新我的列表视图吗?我不知道如何将它与我的静态变量同步。 这是我学习多线程的第一个项目...我有点懵,连我的老师都帮不了。
一方面,发布的代码中没有足够的上下文来全面了解您的问题准确。但是,我们可以仅从您发布的代码中推断出问题所在。
首先,让我们试着回答你的两个问题。我们可能可以推断出以下内容:
这里的代码:
if (e.UserState != null)
{
Data.Add(new Operations()
{
id = rnd.Next(1,999),
name = Factory.name,
qtyStock = Factory.Stock,
averageStock = Factory.AverageStock,
week = Factory.Week
});
listview.ItemsSource = Data;
}
您正在使用 Windows Forms 后台线程对象来尝试更新 WPF GUI 对象,该对象只能在主 GUI 线程上完成。还有一个明显的禁忌,即永远不要从非 UI 线程更新 GUI 对象。使用 BackgroundWorker
在线程 (foreground/background)、上下文和执行方面也有其自身的问题,因为它依赖于 Dispatcher
和 SynchronizationContexts
来完成工作。
然后就是好奇在这一行一遍又一遍地设置绑定:
listview.ItemsSource = Data;
让我们在其中插入一个图钉...
正如其他评论者已经指出的那样,您的 while 循环中没有退出策略:
while (Factory.Week < max) // max = 52
{
if (worker.CancellationPending) // also this isn't reacting to worker.CancelAsync();
e.Cancel = true;
// My teacher tried to call my threads from here, but it breaks the purpose of having
// two threads as he was just calling 52 times two functions back to back and therefore
// wasn't "randomizing" the transactions.
int progressPercentage = Convert.ToInt32(((double)(Factory.Week) / max) * 100);
(sender as BackgroundWorker).ReportProgress(progressPercentage, Factory.Week);
}
但这不是更大的问题...除了when/howmisuse/misunderstandingmany/how使用线程之外,似乎没有任何一种线程同步种类。无法以这种方式预测或跟踪生命周期的线程执行。
此时问题在技术上或多或少得到了回答,但我觉得这只会让您更加沮丧,并且不会比开始时更好。所以也许基本设计的快速速成课程可能有助于理顺这个烂摊子,这是你的老师应该做的。
假设您正在从事软件开发,并且由于您在此处选择了 WPF 作为 "breadboard" 可以这么说,您可能会遇到 MVC(模型视图控制器)或 MVVM(模型视图视图)等术语-模型)。您还可能会遇到设计原则,例如 SOLID、关注点分离以及将事物分组到服务中。
您此处的代码是所有这些框架和原则为何存在的完美示例。让我们看看您遇到的一些问题以及如何解决这些问题:
您将线程代码(逻辑和服务 - 控制器 [松散地说])与表示代码(列表视图更新 - 视图)和集合更新(你的可观察集合 - 模型)混合在一起。这就是(许多)您在编码、修复和维护手头问题时遇到困难的原因之一。要清理它,请将其分离出来(关注点分离)。您甚至可以将每个操作移动到其自己的 class 中,并为 class(服务/微服务)添加 interface/API。
并不是所有的事情都需要用线程来解决。但是现在,让我们学习爬行,然后在我们 运行 之前走路。在开始学习 async/await 或 TPL(任务并行库)之前,让我们先了解一下老派。买一本好书……甚至可以找到 20 年前的东西……走老路,学习如何使用 ThreadPool 和内核同步对象,如互斥锁、事件等,以及如何在线程之间发送信号。一旦你掌握了它,然后学习 TPL 和 async/await.
不要过溪。不要混用 WinForms、WPF,我什至看到 Console.WriteLine
.
了解数据绑定,尤其是它在 WPF 中的工作原理。 ObservableCollection
是你的好友,将你的ItemsSource
绑定一次,然后更新ObservableCollection
离开单独的 GUI 对象。
希望这能帮助你理顺代码并得到东西运行宁。
祝你好运!
我有一个 WPF 应用程序,它由两个线程组成,模拟企业在 52 周内生产和销售商品(每周只允许一次交易)。我还需要使用后台工作者,以便我可以在列表视图中显示数据。截至目前,我的 UI 在单击 simulate
时冻结,但我可以看到输出仍在调试终端中运行。我已经尝试了所有我能想到的方法,老实说,我得到了我老师的帮助,甚至他也找不到可行的解决方案。
- 当我调用
Simulate()
时,是什么冻结了我的 UI? - 当我的代码不同并且我的 UI 没有冻结时,我的列表视图永远不会更新,因为它似乎
DataProgress()
不起作用 —e.UserStart
永远不会迭代。
模拟按钮调用:
private void Simulate(object sender, RoutedEventArgs e)
{
// Declare BackgroundWorker
Data = new ObservableCollection<Operations>();
worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.RunWorkerAsync(52);
worker.DoWork += ShowData;
worker.ProgressChanged += DataProgress;
worker.RunWorkerCompleted += DataToDB;
Production = new Production(qtyProduction, timeExecProd);
Sales = new Sales(qtySales, timeExecSales);
Thread prod = new Thread(Production.Product);
prod.Start();
Thread.Sleep(100);
Thread sales = new Thread(Sales.Sell);
sales.Start();
}
DoWork : ShowData() :
Console.WriteLine("Simulation started | Initial stock : 500");
Production = new Production(qtyProduction, timeExecProd);
Sales = new Sales(qtySales, timeExecSales);
while (Factory.Week < max) // max = 52
{
if (worker.CancellationPending) // also this isn't reacting to worker.CancelAsync();
e.Cancel = true;
// My teacher tried to call my threads from here, but it breaks the purpose of having
// two threads as he was just calling 52 times two functions back to back and therefore
// wasn't "randomizing" the transactions.
int progressPercentage = Convert.ToInt32(((double)(Factory.Week) / max) * 100);
(sender as BackgroundWorker).ReportProgress(progressPercentage, Factory.Week);
}
ProgressChanged : DataProgress() :
if (e.UserState != null) // While using debugger, it looks like this is called over & over
{
Data.Add(new Operations()
{
id = rnd.Next(1,999),
name = Factory.name,
qtyStock = Factory.Stock,
averageStock = Factory.AverageStock,
week = Factory.Week
});
listview.ItemsSource = Data;
}
RunWorkerCompleted : DataToDB() :
// Outputs "Work done" for now.
如果您想知道当我调用我的线程时会发生什么,它看起来像这样:
卖出() :
while (Factory.Week <= 52)
{
lock (obj)
{
// some math function callings¸
Factory.Week++;
}
Thread.Sleep(timeExecSales);
}
我应该使用第三个线程来更新我的列表视图吗?我不知道如何将它与我的静态变量同步。 这是我学习多线程的第一个项目...我有点懵,连我的老师都帮不了。
一方面,发布的代码中没有足够的上下文来全面了解您的问题准确。但是,我们可以仅从您发布的代码中推断出问题所在。
首先,让我们试着回答你的两个问题。我们可能可以推断出以下内容:
这里的代码:
if (e.UserState != null)
{
Data.Add(new Operations()
{
id = rnd.Next(1,999),
name = Factory.name,
qtyStock = Factory.Stock,
averageStock = Factory.AverageStock,
week = Factory.Week
});
listview.ItemsSource = Data;
}
您正在使用 Windows Forms 后台线程对象来尝试更新 WPF GUI 对象,该对象只能在主 GUI 线程上完成。还有一个明显的禁忌,即永远不要从非 UI 线程更新 GUI 对象。使用 BackgroundWorker
在线程 (foreground/background)、上下文和执行方面也有其自身的问题,因为它依赖于 Dispatcher
和 SynchronizationContexts
来完成工作。
然后就是好奇在这一行一遍又一遍地设置绑定:
listview.ItemsSource = Data;
让我们在其中插入一个图钉...
正如其他评论者已经指出的那样,您的 while 循环中没有退出策略:
while (Factory.Week < max) // max = 52
{
if (worker.CancellationPending) // also this isn't reacting to worker.CancelAsync();
e.Cancel = true;
// My teacher tried to call my threads from here, but it breaks the purpose of having
// two threads as he was just calling 52 times two functions back to back and therefore
// wasn't "randomizing" the transactions.
int progressPercentage = Convert.ToInt32(((double)(Factory.Week) / max) * 100);
(sender as BackgroundWorker).ReportProgress(progressPercentage, Factory.Week);
}
但这不是更大的问题...除了when/howmisuse/misunderstandingmany/how使用线程之外,似乎没有任何一种线程同步种类。无法以这种方式预测或跟踪生命周期的线程执行。
此时问题在技术上或多或少得到了回答,但我觉得这只会让您更加沮丧,并且不会比开始时更好。所以也许基本设计的快速速成课程可能有助于理顺这个烂摊子,这是你的老师应该做的。
假设您正在从事软件开发,并且由于您在此处选择了 WPF 作为 "breadboard" 可以这么说,您可能会遇到 MVC(模型视图控制器)或 MVVM(模型视图视图)等术语-模型)。您还可能会遇到设计原则,例如 SOLID、关注点分离以及将事物分组到服务中。
您此处的代码是所有这些框架和原则为何存在的完美示例。让我们看看您遇到的一些问题以及如何解决这些问题:
您将线程代码(逻辑和服务 - 控制器 [松散地说])与表示代码(列表视图更新 - 视图)和集合更新(你的可观察集合 - 模型)混合在一起。这就是(许多)您在编码、修复和维护手头问题时遇到困难的原因之一。要清理它,请将其分离出来(关注点分离)。您甚至可以将每个操作移动到其自己的 class 中,并为 class(服务/微服务)添加 interface/API。
并不是所有的事情都需要用线程来解决。但是现在,让我们学习爬行,然后在我们 运行 之前走路。在开始学习 async/await 或 TPL(任务并行库)之前,让我们先了解一下老派。买一本好书……甚至可以找到 20 年前的东西……走老路,学习如何使用 ThreadPool 和内核同步对象,如互斥锁、事件等,以及如何在线程之间发送信号。一旦你掌握了它,然后学习 TPL 和 async/await.
不要过溪。不要混用 WinForms、WPF,我什至看到
Console.WriteLine
.了解数据绑定,尤其是它在 WPF 中的工作原理。
ObservableCollection
是你的好友,将你的ItemsSource
绑定一次,然后更新ObservableCollection
离开单独的 GUI 对象。
希望这能帮助你理顺代码并得到东西运行宁。
祝你好运!