C# 如何使用按钮停止窗体中长期运行的子例程?

C# 如何使用按钮停止窗体中长期运行的子例程?,c#,winforms,console-application,C#,Winforms,Console Application,我有一个显示WinForms表单的控制台应用程序 在表单中,用户单击按钮1,它运行一个长的子例程。我想要一个按钮2,它可以在任何时候终止子例程。但是,当我单击按钮1时,UI会冻结,直到子例程完成。如何使UI不冻结?您需要按照注释中的建议将其设置为多线程。旧的方法是管理自己的线程。然后是背景工人(又便宜又容易)。现在,您有了其他选项,例如任务库 记住-在UI线程上运行的任何操作都会阻止UI发送和接收事件,直到该操作完成 查看组件您的长时间运行的代码正在阻塞UI线程,因此在代码执行完毕之前,您不能再

我有一个显示WinForms表单的控制台应用程序


在表单中,用户单击按钮1,它运行一个长的子例程。我想要一个按钮2,它可以在任何时候终止子例程。但是,当我单击按钮1时,UI会冻结,直到子例程完成。如何使UI不冻结?

您需要按照注释中的建议将其设置为多线程。旧的方法是管理自己的线程。然后是背景工人(又便宜又容易)。现在,您有了其他选项,例如任务库


记住-在UI线程上运行的任何操作都会阻止UI发送和接收事件,直到该操作完成

查看组件

您的长时间运行的代码正在阻塞UI线程,因此在代码执行完毕之前,您不能再单击第二个按钮,也不能以任何方式与UI交互

您需要将长期运行的代码移动到单独的线程。有多种(和更新的)方法可以做到这一点,但其中一种方法是。它非常容易学习,并且包含了一些很好的功能,比如取消线程

下面是一个简短的WinForms应用程序演示。必须显式启用取消线程的功能。在本例中,
while
循环无限期地继续,但每100ms检查一次,以查看是否有取消该循环的请求。单击第二个按钮时,将发送取消请求,线程结束

public partial class Form1 : Form
{
    private BackgroundWorker bg;

    public Form1()
    {
        InitializeComponent();

        bg = new BackgroundWorker
             {
                 WorkerSupportsCancellation = true
             };

        bg.DoWork += (sender, args) =>
        {
            while (true)
            {
                Thread.Sleep(100);
                if (bg.CancellationPending)
                    break;
            }

            MessageBox.Show("Done!");
        };
    }

    private void button1_Click(object sender, EventArgs e)
    {
        bg.RunWorkerAsync();
    }

    private void button2_Click(object sender, EventArgs e)
    {
        bg.CancelAsync();
    }
}
继chouaib的评论之后,在WinForms环境中使用BackgroundWorker的另一个好处是,您可以将其拖放到设计器中,类似于菜单、计时器等。然后您可以在“属性”面板中访问其成员,将“WorkerSupportsCancellation”设置为
true
,订阅事件等


根据您的评论:

“有没有办法运行此后台进程并能够更新主用户窗体?我一直在获取“跨线程操作无效控件,从除…”之外的线程访问”我想运行长时间运行的后台操作,并让它使用标签中的文本更新主UI(如进度的百分比)”

如果希望在线程运行时更新UI,则应通过ProgressChanged事件进行更新。首先,启用该选项并订阅事件:

bg.WorkerReportsProgress = true;

bg.ProgressChanged += bg_ProgressChanged;
void bg_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    var message = e.UserState.ToString();
    var percent = e.ProgressPercentage;

    lblStatus.Text = message + " " + percent;
}
然后,当您想要更新UI时,调用
ReportProgress()
。您可以传回完成百分比和一些文本,例如:

bg.ReportProgress(50, "Almost there...");
最后,从事件内部更新UI:

bg.WorkerReportsProgress = true;

bg.ProgressChanged += bg_ProgressChanged;
void bg_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    var message = e.UserState.ToString();
    var percent = e.ProgressPercentage;

    lblStatus.Text = message + " " + percent;
}

它是控制台应用程序还是WinForms应用程序?我完全搞不懂它有点模糊,没有一段代码就看不清楚,我只是猜测,试试
Application.DoEvents()
@chouaib——永远不要在OP中使用
Application.DoEvents()
,请澄清您的问题,因为您认为这是一个控制台应用程序令人费解。这是一个控制台应用程序。我放了一个UI,以便有人在没有命令提示的情况下使用exe。我有各种各样的终端用户,一些noob和一些脚本编写人员。大多数noobs都不想使用命令提示符。很好的教程,我建议您通过从工具箱的组件拖放一个
BackgroundWorker
来定义
Form()
中的所有内容,然后执行设置('WorkSupportCancellation=true')。这个BackgroundWorker可以工作(运行线程也是如此),但是有没有办法运行这个后台进程并能够更新主用户表单呢?我不断得到“cross thread operation not valid control accessed from thread to the…”之外的其他线程。我想运行长时间运行的后台操作,并让它用标签中的文本(比如进度的百分比)更新主UI。这更多的是一个注释,而不是答案。