【问题标题】:How can a new Form be run on a different thread in C#?如何在 C# 中的不同线程上运行新表单?
【发布时间】:2010-08-14 10:28:40
【问题描述】:

我只是尝试在每次单击按钮时运行一个新线程,这应该会创建一个新表单。我在 MainForm 中的按钮单击事件中尝试了这个:

private void button1_Click(object sender, EventArgs e)
{
    worker1 = new Thread(new ThreadStart(thread1));
    worker2 = new Thread(new ThreadStart(thread2));

    worker1.Start();
    worker2.Start();
}

private void thread1()
{
    SubForm s = new SubForm();
    s.Show();
}

private void thread2()
{
    SubForm s = new SubForm();
    s.Show();
}

Subform 按钮点击事件中的代码如下:

private void button1_Click(object sender, EventArgs e)
{
    int max;
    try
    {
        max = Convert.ToInt32(textBox1.Text);
    }
    catch
    {
        MessageBox.Show("Enter numbers", "ERROR");
        return;
    }

    progressBar1.Maximum = max;

    for ( long i = 0; i < max; i++)
    {
        progressBar1.Value = Convert.ToInt32(i);
    }          
}

这是正确的方法吗?因为我试图打开两个独立的表单,所以一个线程中的操作应该不会影响另一个线程。

或者 BackGroundworker 是实现这一点的解决方案吗?如果是的话,有人可以帮我吗?

【问题讨论】:

  • 我认为 Windows 应用程序每个进程只有一个 GUI 事件循环?因此所有 UI 的东西都需要在主线程中完成? (CMIIW)
  • @user:你具体想解决什么问题?
  • 如果我在主窗体中单击按钮两次,它当然会打开两个新窗体,即带有进度条的子窗体和所有窗体。如果向第一个子窗体提供了一个输入,并且它运行了很长时间(比如我有一个没有结束的进度条),我无法对打开的第二个子窗体执行任何操作。所以我想到了单独打开子表单,彼此独立..
  • 没有“主线程”,任何线程都可以运行自己的消息循环。

标签: c# .net winforms multithreading message-loop


【解决方案1】:

您不需要在单独的线程中运行表单。您可以在多个表单上正常调用s.Show()。他们不会互相阻挡。

当然,如果您正在做其他事情,例如某种计算或其他需要很长时间的任务,那么您应该在单独的线程,但不是表单。

这里有一些代码可以让你创建一个进度条来显示一个长过程的进度。请注意,每次从线程内部访问表单时,您都必须使用 .Invoke(),它实际上会安排调用在 GUI 线程准备好时运行。

public void StartLongProcess()
{
    // Create and show the form with the progress bar
    var progressForm = new Subform();
    progressForm.Show();
    bool interrupt = false;

    // Run the calculation in a separate thread
    var thread = new Thread(() =>
    {
        // Do some calculation, presumably in some sort of loop...
        while ( ... )
        {
            // Every time you want to update the progress bar:
            progressForm.Invoke(new Action(
                () => { progressForm.ProgressBar.Value = ...; }));

            // If you’re ready to cancel the calculation:
            if (interrupt)
                break;
        }

        // The calculation is finished — close the progress form
        progressForm.Invoke(new Action(() => { progressForm.Close(); }));
    });
    thread.Start();

    // Allow the user to cancel the calculation with a Cancel button
    progressForm.CancelButton.Click += (s, e) => { interrupt = true; };
}

【讨论】:

  • @Timwi:谢谢你的回答。但我已经试过了。我的要求实际上是不同的。假设我打开了两个子表单,并且在 SubForm1 中运行了一个较长的进程,我现在可以访问 SubForm2 吗??
  • @Timwi:对不起,我第一次没有看到你的更新。所以这就是我试图做的正是“在一个单独的线程中运行它,而不是像你所说的那样在表单中运行”,但我想知道如何?抱歉,我是新手,刚刚开始学习。
  • @user403489,建议不要在SubForm1中运行长进程,而是在BackgroundWorker(或Thread,但BackgroundWorker使GUI线程交互更易于管理)中运行那个长进程。否则,您会使 SubForm1 无响应,这会导致丑陋的绘图伪影(不重绘)等等。
  • @Timwi:非常感谢。我会试试这个。
【解决方案2】:

虽然我不是 100% 知道有什么说运行完全独立的表单在它们自己的线程中执行完全隔离的操作在任何方面都是危险的,但在单个线程上运行所有 UI 操作通常被认为是一种好的做法。

您可以通过让您的 Subform 类使用 BackgroundWorker 来支持这一点。显示表单后,启动 BackgroundWorker,以便它处理您需要的任何内容。

然后您可以简单地在您的 GUI 线程上创建您的子表单的新实例并显示它们。表单将显示并在另一个线程上开始其操作。

这样,UI 将在 GUI 线程上运行,但表单正在运行的操作将在 ThreadPool 线程上运行。

更新

下面是您的后台工作处理程序可能看起来的示例 - 请注意(像往常一样)这只是我的想法,但我认为您可以了解基本原则。

在名为 worker 的表单中添加一个 BackgroundWorker。将其连接到以下事件处理程序:

void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // Executed on GUI thread.
    if (e.Error != null)
    {
        // Background thread errored - report it in a messagebox.
        MessageBox.Show(e.Error.ToString());
        return;
    }

    // Worker succeeded.
}

void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    // Executed on GUI thread.
    progressBar1.Value = e.ProgressPercentage;
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    // Executed on ThreadPool thread.
    int max = (int)e.Argument;

    for (long i = 0; i < max; i++)
    {
        worker.ReportProgress(Convert.ToInt32(i));
    }
}

您的点击处理程序如下所示:

void button1_Click(object sender, EventArgs e)
{
    int max;

    try
    {
        // This is what you have in your click handler,
        // Int32.TryParse is a much better alternative.
        max = Convert.ToInt32(textBox1.Text);
    }
    catch
    {
        MessageBox.Show("Enter numbers", "ERROR");
        return;
    }

    progressBar1.Maximum = max;

    worker.RunWorkerAsync(max);
}

希望对你有帮助。

【讨论】:

  • 感谢亚历克斯·汉弗莱先生。我确实理解您的回答,BackgroundWorker 是最好的做法,但您能在这个例子中帮助我吗?因为,我知道它的作用,但是我看到的所有例子都太长了,无法理解。所以为了理解简单的东西,我举了一个小例子。我希望这对其他人也有用。谢谢。
  • @user403489:我已经更新了一些示例代码 - 我希望它能让您了解如何使用 BackgroundWorker。
  • 谢谢亚历克斯先生。您的帮助对像我这样的人很有用。
  • @Alex:我试过了,它有效。非常感谢。我用过'Application.Run(s); ' 而不是我最初使用的 s.show 和 'Int32.TryParse' 似乎不起作用,'没有重载方法'TryParse' 需要 '1' 参数'是我得到的错误。我搜索了解决方案。我将“max”声明为“int”,并且只使用了“Int32.TryParse”。但我仍然得到那个错误。 neway,我对 GUI 表单的主要疑问得到了解决。谢谢大家。
  • @SLp:我说使用 Int32.TryParse 的原因是为了避免吞下异常。使用类似 int max = 0; if(Int32.TryParse(textBox1.Text, out max)){ // 文本是一个整数。 } else { // 文本不是有效的整数。 } - 您是否可以单击您选择的答案旁边的刻度线,以便阅读您的问题时遇到相同问题的人可以确定最佳方法?干杯。
【解决方案3】:

试试这个。它使用自己的消息队列在自己的线程上运行新表单,等等。 运行这段代码:

new Thread(new ThreadStart(delegate
{
   Application.Run(new Form());
})).Start();

使用Thread.CurrentThread.GetHashCode() 测试在不同线程上运行。

【讨论】:

    【解决方案4】:

    可以在不同的线程上运行不同的表单。我知道有两个警告:

    1. 任何一种形式都不能是另一个形式的 MDI 客户端。当表单具有不同的线程时,试图使一个表单成为另一个表单的 MDI 客户端将会失败。
    2. 如果一个对象将向多个表单发送事件并且所有表单都使用同一个线程,则可以在引发事件之前将事件同步到主线程。否则,必须异步引发事件,并且每个表单必须为传入事件执行自己的同步机制。

    显然,最好不要让任何窗口的 UI 线程被阻塞,但是为单独的窗口使用单独的线程可能是一个不错的选择。

    【讨论】:

      猜你喜欢
      • 2023-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-22
      • 2020-03-13
      • 2011-11-24
      • 2022-01-04
      相关资源
      最近更新 更多