(C#) BackgroundWorker() ProgressChanged 不工作

(C#) BackgroundWorker() ProgressChanged not working

我有一个 WPF 应用程序,它由两个线程组成,模拟企业在 52 周内生产和销售商品(每周只允许一次交易)。我还需要使用后台工作者,以便我可以在列表视图中显示数据。截至目前,我的 UI 在单击 simulate 时冻结,但我可以看到输出仍在调试终端中运行。我已经尝试了所有我能想到的方法,老实说,我得到了我老师的帮助,甚至他也找不到可行的解决方案。


  1. 当我调用 Simulate() 时,是什么冻结了我的 UI?
  2. 当我的代码不同并且我的 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)、上下文和执行方面也有其自身的问题,因为它依赖于 DispatcherSynchronizationContexts 来完成工作。

然后就是好奇在这一行一遍又一遍地设置绑定:

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、关注点分离以及将事物分组到服务中。

您此处的代码是所有这些框架和原则为何存在的完美示例。让我们看看您遇到的一些问题以及如何解决这些问题:

  1. 您将线程代码(逻辑和服务 - 控制器 [松散地说])与表示代码(列表视图更新 - 视图)和集合更新(你的可观察集合 - 模型)混合在一起。这就是(许多)您在编码、修复和维护手头问题时遇到困难的原因之一。要清理它,请将其分离出来(关注点分离)。您甚至可以将每个操作移动到其自己的 class 中,并为 class(服务/微服务)添加 interface/API。

  2. 并不是所有的事情都需要用线程来解决。但是现在,让我们学习爬行,然后在我们 运行 之前走路。在开始学习 async/await 或 TPL(任务并行库)之前,让我们先了解一下老派。买一本好书……甚至可以找到 20 年前的东西……走老路,学习如何使用 ThreadPool 和内核同步对象,如互斥锁、事件等,以及如何在线程之间发送信号。一旦你掌握了它,然后学习 TPL 和 async/await.

  3. 不要过溪。不要混用 WinForms、WPF,我什至看到 Console.WriteLine.

  4. 了解数据绑定,尤其是它在 WPF 中的工作原理。 ObservableCollection是你的好友,将你的ItemsSource绑定一次,然后更新ObservableCollection离开单独的 GUI 对象。

希望这能帮助你理顺代码并得到东西运行宁。

祝你好运!