成功运行BackgroundWorker() 后程序的其余部分不执行

Posted

技术标签:

【中文标题】成功运行BackgroundWorker() 后程序的其余部分不执行【英文标题】:After running successfully BackgroundWorker() the rest of the program is not executed 【发布时间】:2021-01-20 02:23:33 【问题描述】:

我正在使用 BackgroundWorker() 在后台执行长时间运行的查询,同时我正在显示我的执行正在运行的弹出窗口。

这是我如何调用 bg_worker()

using System.Windows;
using System.ComponentModel;
using System.Threading;
using System;
using System.IO;
using System.Data.SqlClient;
using System.Windows.Input;

namespace TestEnvironment

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>

    public partial class ProgressBarTemplate : Window
    
        private CreateProjectScreen _CreateProjectScreen;
        private LoginScreen _LoginScreen;

        public ProgressBarTemplate()
        
            InitializeComponent();
        

        public static int RunCalculationsMethod(string connectionstring, string foldername)
        
            bool exists = Directory.Exists(foldername);

            if (!exists)
            
                Directory.CreateDirectory(foldername);
            

            try
            
                using (SqlConnection sqlConnection = new SqlConnection(connectionstring))
                
                    var calculations_query = "SELECT * FROM table1");

                    using SqlCommand sqlCommand = new SqlCommand(calculations_query, sqlConnection);

                    sqlConnection.Open();

                    sqlCommand.CommandTimeout = 60 * 10;

                    int NumbderOfRecords = sqlCommand.ExecuteNonQuery();

                    return NumbderOfRecords;
                
            

            catch (Exception ex)
            
                MessageBox.Show(ex.ToString(), "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return -100;
            
        

        private void Window_ContentRendered(object sender, EventArgs e)
        
            BackgroundWorker worker = new BackgroundWorker();
            
            worker.DoWork += worker_DoWork;
            worker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
            worker.RunWorkerAsync();           
        

        void worker_DoWork(object sender, DoWorkEventArgs e)
        
            int IsSuccessful = RunCalculationsMethod("Server=localhost;Database=DB_Name;Integrated Security=SSPI", String.Format("C:\\folder_path\\"));
        

        void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        
            // This is called on the UI thread when the DoWork method completes
            // so it's a good place to hide busy indicators, or put clean up code

            try
            
                this.Close();
                MessageBox.Show("DQ Calculations completed successfully", "Information", MessageBoxButton.OK, MessageBoxImage.Information);
            

            catch (Exception ex)
            
                MessageBox.Show(ex.ToString(), "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            
        
    


上面的代码放在一个叫做 ProgressBarTemplate() 的窗口中

我想要的是在按钮点击时调用 background_worker,在我的 MainWindow 中放置一个按钮

所以我的 MainWindow 有以下按钮点击

private void RunCalculationsButton_Click(object sender, RoutedEventArgs e)
        
            //RunCalculationsMethod(SQLServerConnectionDetails(), String.Format("C:\\DQ_Findings_0", HomePageTab.Header.ToString().Split(" - ")[1]));
            try
            
                Application.Current.Dispatcher.Invoke((Action)delegate
                
                    ProgressBarTemplate win_progressbar = new ProgressBarTemplate();
                    win_progressbar.Show();
                    //RunCalculationsMethod(SQLServerConnectionDetails(), String.Format("C:\\DQ_folder_test\\Findings\\"));
                ); // The code runs up to this point.
                
                //The code below is not executed for a reason, which I am trying to solve with this question
                List<SucessfulCompletion> reportsucessfulcompletion = new List<SucessfulCompletion>();
                reportsucessfulcompletion = SuccessfulCalculationsTimestamp(SQLServerConnectionDetails());

                if (reportsucessfulcompletion[0].Result==1)
                
                    //Enable is only if successfull
                    PreviewCalculationsButton.IsEnabled = true;
                    PreviewReportButton.IsEnabled = true;

                    //add textbox of sucess
                    TickButtonSymbolCalculations.Visibility = Visibility.Visible;
                    SQLSuccessfulTextCalculations.Visibility = Visibility.Visible;
                    XerrorSymbolCalculations.Visibility = Visibility.Hidden;
                    SQLFailedTextCalculations.Visibility = Visibility.Hidden;
                    SQLSuccessfulTextCalculations.Text = String.Format("Completed On: 0", reportsucessfulcompletion[0].Timestampvalue);
                
                else
                
                    //add textbox of fail
                    TickButtonSymbolCalculations.Visibility = Visibility.Hidden;
                    SQLSuccessfulTextCalculations.Visibility = Visibility.Hidden;
                    XerrorSymbolCalculations.Visibility = Visibility.Visible;
                    SQLFailedTextCalculations.Visibility = Visibility.Visible;
                    SQLFailedTextCalculations.Text = String.Format("Failed On: 0", reportsucessfulcompletion[0].Timestampvalue);
                
            

            catch (Exception ex)
            
                MessageBox.Show(ex.ToString(), "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            
        

单击按钮时,我通过调用窗口 ProgressBarTemplate() 来启动 bg_worder。尽管在完成任务后代码生成一些文本并启用某些按钮的可见性,但它们并没有被执行。为什么会这样?我错过了什么吗?

【问题讨论】:

这是可怕的代码。我不知道 UI 何时结束,线程逻辑何时开始。您的代码可能会在调用方法(即 UI 线程)中完成所有操作。 @Bizhan 问题出在我的第一个代码 sn-p(又名 BG Worker)还是关于 MainWindow(第二个代码 sn-p)? :) 我认为您不应该通过调度程序创建进度条。您已经在按钮单击事件处理程序的 UI 线程中,因此您应该能够直接创建它。 @allan 如果你提到Application.Current.Dispatcher.Invoke((Action)delegate ...); 我这样做是因为 C# 给我一个关于 STA 线程错误的错误 @NikSp 忘记 BGW。这是一个自 2012 年以来完全被 Task.Run 取代的过时类。与其将计算放在进度窗口中,不如在 clickrendered 处理程序中使用简单的 var results=await Task.Run(()=&gt;SomeHeavyComputing()); 就足够了,允许您在之前更新 UI 和在没有 Invoke 的异步操作之后 【参考方案1】:

代码有点不清楚,所以我将发布它应该如何完成。

BGW 是一个过时的类,自 2012 年起被 Task.Run 和 Progress< T> 完全取代。当 async/await 可用时,无需使用 BGW 或 Invoke

您可以使用异步事件处理程序,在后台执行任何计算并在后台操作完成后更新 UI,而不是将计算放在进度窗口中。进度表似乎没有报告任何进度,所以只需要显示和隐藏它。代码可以很简单:

private async void RunCalculationsButton_Click(object sender, RoutedEventArgs e)

    var win_progressbar = new ProgressBarTemplate();
    win_progressbar.Show();
    try
    
        //Update the UI
        var results=await Task.Run(()=> RunCalculationsMethod(...));
        //Update the UI
    
    finally
    
        win_progressbar.Close();
    

try/finally 用于确保即使出现错误也关闭表单。

进度报告

进度报告可通过Progress 类和IProgress 接口获得。 IProgress&lt;T&gt; 允许后台任务向实现接口的类发送强类型消息。 Progress&lt;T&gt; 实现确保消息在创建它的线程中处理,通常是 UI 线程。

假设这是消息类型:

class MyProgress

    public int Percentget;set;
    public string Message get;set;
 

RunCalculationsMethod 可以修改为接受和使用IProgress&lt;MyProgress&gt;

public static int RunCalculationsMethod(string connectionstring, string foldername, 
                                        IProgress<MyProgress> progress)


    progress.Report(new MyProgressPercent=0,Message="Starting";
    ....
    progress.Report(new MyProgressPercent=100,Message="Finished";

事件处理程序只需要创建一个Progress&lt;MyProgress&gt; 并提供一个更新 UI 的方法。假设ProgressBarTemplate 有这样一个方法,称为Update(string,int)

private async void RunCalculationsButton_Click(object sender, RoutedEventArgs e)

    var win_progressbar = new ProgressBarTemplate();
    IProgress<MyProgress> pg=new Progress<MyProgress>(pg=>
                        win_progressbar.Update(pg.Message,pg.Percent));
    win_progressbar.Show();
    try
    
        //Update the UI
        var results=await Task.Run(()=> RunCalculationsMethod(...,pg));
        //Update the UI
    
    finally
    
        win_progressbar.Close();
    

你可以在Async in 4.5: Enabling Progress and Cancellation in Async APIs找到更详尽的解释

【讨论】:

Panagiotis 求答案。如果我采用这种方法,那么有一个进度条有什么意义呢?我的意思是因为我从主窗口而不是从 ProgressBar 模板窗口调用 RunCalculationsMethod()。还有如何使用Progress&lt;T&gt; @NikSp 我更新了答案,显示如何使用Progress&lt;T &gt; 并链接到解释进度报告和取消的文章。进度表只能用于显示进度。这样它就可以被应用程序中的任何异步操作重用。 好的,现在我的 ProgressBarTemplate 窗口有这个代码void worker_DoWork(object sender, DoWorkEventArgs e) int IsSuccessful = RunCalculationsMethod("Server=localhost;Database=ΓειαΣου;Integrated Security=SSPI", String.Format("folder_path")); 我应该删除它吗? 从该表单中删除它。从事件处理程序调用它。进度表应该只显示进度。并彻底移除 BGW 是的。顺便说一句,既然您是从数据科学开始的,请查看 Parallel and Asynchronous Programming 和 The Server-SIde Story: Παράλληλος και ασύγχρονος προγραμματισμός στο .NET。并行、并发和异步是完全不同的野兽,需要不同的类【参考方案2】:

您应该使用 Application.Current.Dispatcher.Invoke,因为您尝试访问 UI 元素。除主线程外,其他线程无法访问ui元素。

将您的代码替换为

Dispatcher.Invoke(() =>
 // UI access code
);

【讨论】:

或者只使用async/await 所以不需要Invoke @PanagiotisKanavos 通常,您的答案是收集。但有时在使用嵌套 async/await 时不起作用。 它一直在工作。当它看起来没有时,它是一个编码错误,例如有人试图从异步方法中更新 UI,或者使用 ConfigureAwait(false) 没有意识到它做了什么 @PanagiotisKanavos 例如。像这个按钮示例。 private async void OnButtonClicked(object sende, RoutedEventArgs e) 此区域可以进行 UI 访问。 await Task.run(async() => 此区域无法访问 UI。 这是一个编码错误。您正在尝试从后台线程更新 UI。如果您想从后台线程中报告进度,请使用IProgress&lt;T&gt; 接口。自 2012 年起不再需要 Invoke

以上是关于成功运行BackgroundWorker() 后程序的其余部分不执行的主要内容,如果未能解决你的问题,请参考以下文章

元宇宙:步入VR的后程还是开创自己的未来

为啥 BackgroundWorker 的 Progress 事件运行在不同的线程上?

从 BackgroundWorker 运行时 CrystalReportViewer.RefreshReport 挂起

c# 关于backgroundWorker的取消

两个同时运行的线程控件(BackgroundWorker)串数据

将 'BackgroundWorker' 替换为 'Thread'