(C#) BackgroundWorker () ProgressChanged не работает - PullRequest
0 голосов
/ 14 февраля 2020

У меня есть приложение WPF , которое состоит из двух потоков, имитирующих продукцию и продажу продукции предприятия за 52 недели (в неделю разрешена только одна транзакция). Мне также нужно использовать фонового работника, чтобы я мог отображать данные в виде списка. На данный момент мой пользовательский интерфейс зависает при нажатии на simulate, но я вижу, что вывод все еще работает в терминале отладки. Я перепробовал все, что мог придумать, и, если честно, мне помог мой учитель, и даже он не смог найти рабочее решение.

  1. Что блокирует мой пользовательский интерфейс, когда я звоню Simulate()?
  2. Когда мой код отличается и мой пользовательский интерфейс не зависает, мой список никогда не обновляется, потому что кажется, что DataProgress() не делает t работает - 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.

Если вы хотите знать, что происходит, когда я вызываю мои темы, это выглядит так:

Sell ():

while (Factory.Week <= 52)
{
    lock (obj)
    {
        // some math function callings¸
        Factory.Week++;
    }
    Thread.Sleep(timeExecSales);
}

Должен ли я использовать третий поток только для обновления моего списка? Я не вижу, как мне нужно, чтобы это было синхронизировано с моими переменными состояния c. Это мой первый проект по изучению многопоточности ... Я немного не в себе и изумлен, что даже мой учитель не может помочь.

1 Ответ

1 голос
/ 15 февраля 2020

С одной стороны, в размещенном коде недостаточно контекста, чтобы получить полную картину, чтобы ответить на ваши вопросы точно . Однако мы можем вывести, что происходит не так, просто из кода, который вы опубликовали.

Во-первых, давайте попробуем ответить на два ваших вопроса. Мы можем вывести следующее:

Этот код здесь:

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. Существует также очевидное «нет-нет» - никогда не обновлять GUI объекты из не-пользовательских потоков. Использование BackgroundWorker также имеет свои проблемы с многопоточностью (передний план / фон), контекстами и выполнением, так как для выполнения работы используются Dispatcher и SynchronizationContexts.

Тогда возникает любопытство установки привязки снова и снова в этой строке:

listview.ItemsSource = Data;

Давайте на мгновение вставим булавку в это ...

Есть, как и другие указатель на комментатор уже отсутствует, у вас нет стратегии выхода l oop:

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);
}

Но это не самая большая проблема ... в дополнение к неправильному использованию / неправильному пониманию того, когда / сколько / как использовать Похоже, что нет никакой синхронизации потоков. Таким образом, нет никакого способа предсказать или отследить выполнение времени жизни потока.

На данный момент на вопрос технически более или менее ответили, но я чувствую, что это просто оставит вас более расстроенным и не лучше, чем ты начал. Так что, может быть, быстрый краткий курс sh по основам дизайна c может помочь исправить этот беспорядок, что должен был сделать ваш учитель.

Предполагая, что вы занимаетесь разработкой программного обеспечения, и поскольку вы выбрали WPF здесь как ваш «макет», так сказать, вы, скорее всего, встретите такие термины, как MVC (контроллер вида модели) или MVVM (вид модели модели). Скорее всего, вы также столкнетесь с принципами проектирования, такими как SOLID, разделение задач и группировка вещей в сервисы.

Ваш код здесь - прекрасный пример того, почему существуют все эти платформы и принципы. Давайте рассмотрим некоторые проблемы, с которыми вы столкнулись, и способы их устранения:

  1. У вас есть многопоточный код (logi c и services - controller [свободно говоря]), смешанный с презентацией код (listview update - view) и обновление коллекции (ваша наблюдаемая коллекция - модель). Это одна из причин (из многих), что вы сталкиваетесь с трудностями при написании кода, исправлении и поддержании проблемы под рукой. Чтобы очистить это, отделите это (разделение проблем). Вы можете даже переместить каждую операцию в свой собственный класс с интерфейсом / API для этого класса (сервис / микросервис).

  2. Не все нужно решать с помощью потоков. Но сейчас давайте научимся ползать, а затем идти, прежде чем бежать. Прежде чем вы начнете изучать async / await или TPL (библиотека параллельных задач), давайте go old school. Получить хорошую книгу ... что-то даже из 20 лет go это найти ... go старую школу, и научиться использовать ThreadPool и объекты синхронизации ядра, такие как мьютексы, события и т. Д. c и как сигнализировать между потоками. Как только вы овладеете этим, то узнайте о TPL и async / await.

  3. Не пересекать потоки. Не смешивайте WinForms, WPF и я даже видел Console.WriteLine.

  4. Узнайте о привязке данных и, в частности, как она работает в WPF. ObservableCollection ваш друг, привяжите к нему ItemsSource один раз , затем обновите ObservableCollection и оставьте объект GUI в покое.

Надеюсь, это поможет вам исправить код и запустить его в действие.

Удачи!

...