【问题标题】:Creating a new form from a new thread started within an Load event of a different form从不同表单的 Load 事件中启动的新线程创建新表单
【发布时间】:2012-03-14 04:45:20
【问题描述】:

快速提问:是否可以从另一个表单的 Load 事件中启动的新线程显示对话框/显示表单?

编辑:为什么不能从不同表单的 Load 事件中启动的新线程显示对话框/显示表单?

假定的要求是显示一个加载器表单/螺旋加载文本,可能还有更多/同时加载 MDI 子表单,从而挂起整个应用程序。

要求规定“加载程序表单”不得挂起。因此它需要一个新线程。

到目前为止我已经实现的代码摘要: MDI 家长:

delegate void ManipulateJob();
public void StartJob()
{
    Cursor.Current = Cursors.WaitCursor;

    if (this.InvokeRequired) 
    //case when the Loader is started from a different thread than the main we need to invoke
    {
        System.Diagnostics.Debug.WriteLine("-> MainForm.StartJob  InvokeRequired");
        ManipulateJob callback = new ManipulateJob(StartJob);
        this.Invoke(callback, new object[] { });
    }
    else
    {
        tasks_running++;
        System.Diagnostics.Debug.WriteLine("-> MainForm.StartJob  InvokeNotRequired");

        if ((this.t == null || !this.t.IsAlive)&&tasks_running == 1)
        {
            System.Threading.ThreadStart ts = new System.Threading.ThreadStart(StartDifferent);
            this.t = new System.Threading.Thread(ts);
            this.t.Name = "UI Thread";

            System.Diagnostics.Debug.WriteLine(" **starting thread");
            this.t.Start();
            while (_form==null||!_form.IsHandleCreated) 
            //do not continue until the loader form has been shown when this is enabled
            //the whole program hangs here when StartJob is called within Load event
            {
                System.Threading.Thread.Yield();
            }
        }

    }

    System.Diagnostics.Debug.WriteLine("<- MainForm.StartJob");
}

private static frmLoading _form;
public void StartDifferent()
{
    System.Diagnostics.Debug.WriteLine(" **thread started");
    _form = new frmLoading();
    System.Diagnostics.Debug.WriteLine(" **loader created");

    _form.Icon = this.Icon;
    System.Diagnostics.Debug.WriteLine(" **loader icon set");

    _form.ShowDialog();


    System.Diagnostics.Debug.WriteLine(" **thread terminating");
}

public void StopJob()
{
    if (this.InvokeRequired) //in case this is called from a different thread
    {
        System.Diagnostics.Debug.WriteLine("-> MainForm.StopJob  InvokeRequired");
        ManipulateJob callback = new ManipulateJob(StopJob);
        this.Invoke(callback, new object[] { });
    }
    else
    {
        System.Diagnostics.Debug.WriteLine("-> MainForm.StopJob  InvokeNotRequired");
        if (tasks_running>0&&--tasks_running == 0)
        {
            StopDifferent();
        }
    }
    System.Diagnostics.Debug.WriteLine("<- MainForm.StopJob");

    Cursor.Current = Cursors.Default;
}

delegate void CloseLoadingForm();
public void StopDifferent()
{
    System.Diagnostics.Debug.WriteLine("-> MainForm.StopDifferent");

    try
    {
        if (_form != null && _form.IsHandleCreated)
        {
            CloseLoadingForm callback = new CloseLoadingForm(_form.Close); 
            //_form itself is always on a different thread thus, invoke will always be required
            _form.Invoke(callback);
        }
    }
    finally
    {
        try
        {
            if (this.t != null && this.t.ThreadState == System.Threading.ThreadState.Running)
                this.t.Join();
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine("     MainForm.StopDifferent t.Join() Exception: " + ex.Message);
        }
    }
    System.Diagnostics.Debug.WriteLine("<- MainForm.StopDifferent");
}

子表单示例:

private void frmPricingEvaluationConfig_Load(object sender, EventArgs e)
{
    //taking out the following and putting it before 
    //Form frm = new Form();
    //frm.Show(); works
    if (this.MdiParent is IThreadedParent)
    {
        ((IThreadedParent)this.MdiParent).StopJob();
    }

    // the loader form is not displayed yet

    System.Diagnostics.Debug.WriteLine(" loading pricingEvaluationConfig");
    loadPricingClasses();
    loadEvaluationClasses();

    if (this.MdiParent is IThreadedParent)
    {
        ((IThreadedParent)this.MdiParent).StopJob();
    }
}

//loader form displays here, but is unable to close because the closing has been called

请不要介意命名和有 2 组匹配的开始和停止的事实。

问题是每当我从 form1_Load(object sender, EventArgs e) 中调用 Startjob 时,在 Load 方法完成之前不会显示 _form(AKA 加载器)。

编辑: 换句话说,加载器在显示实际表单之前不会显示,加载器“显示”(显示在屏幕上)的唯一时间是实际表单完成时 onLoad 方法。 :ENDEDIT

如果我从负载处理程序方法中取出 Startjob 并将其放在我声明和 Show() 之前,那么一切都会按照预期的方式工作

编辑:
我假设,在没有任何证据的情况下,在 ShowDialog() 和 Show() 方法中调用的 Form 的 CreateHandle() 方法在某处访问静态属性,检查是否已经创建了任何句柄,如果是,它在当前创建之后堆叠自己的创建句柄创建,因为在整个过程中,代码试图关闭()加载器/飞溅并抛出一个错误,说“无法关闭()正在创建句柄的表单”。所以我假设 CreateHandle() 方法有一些类似于我的

while() { Thread.Yield(); } 

因此创建了 2 个线程/1 个是 Loader\Splash 创建者线程/,它们不断地相互让步。

基本上,我不明白为什么会发生这一切,如果我的假设是正确的,为什么表单应该一个接一个地排队? :ENDEDIT

我很乐意回答有关代码的所有问题和疑虑,请询问我在这里所做的任何事情是否含糊不清,或者您不太了解它的需要。

【问题讨论】:

  • 再深入一点,这个“问题”与 MDI 子父关系有关,因为每当我注释掉声明 //frm.MdiParent = this; 时,一切都按照它应该的方式工作

标签: c# winforms .net-4.0


【解决方案1】:

似乎更合适的解决方案是在单独的线程中执行长时间运行的初始化任务,并在初始化完成后启用 MDI 子窗口(因此允许 MDI 子窗口创建完成,但禁用或保留在单独的加载程序线程完成之前,窗口不可见。加载任务完成后,启用启动屏幕或使子窗口可见)。

我不确定我是否正确地遵循了您当前的代码,但您似乎阻止了 OnLoad 事件。这将阻止 Windows 消息队列,从而阻止应用程序的任何 UI 呈现。

然后,您可以使用任何 WinForms 启动屏幕解决方案,直到执行加载的线程完成,例如

http://www.codeproject.com/Articles/5454/A-Pretty-Good-Splash-Screen-in-C

【讨论】:

  • 感谢您浏览我凌乱的代码并理解它。是的,我正在阻塞队列,但仅在 Splash 显示在不同线程上之前,该线程在 OnLoad 期间启动。所以我假设,由于不同的线程 UI 渲染将在 Splash 的另一个线程上完成?我读过那篇文章,它在谷歌上相当高。问题是 Application.Run() 这使得 Splash 要么不在顶部 ||始终在所有窗口的顶部,并且不涵盖我在任务完成之前显示表单的特定问题。
  • 是的,我完全同意你在一个单独的线程上加载东西并运行一个选框进度条。但不幸的是,其中一项要求是在不破坏传统 VS 编程的情况下添加加载程序,因为在单独的线程中加载内容以使 UI 更友好显然是太多工作且难以理解或类似的事情。
  • UI 渲染是在 1 个线程上完成的,无论表单是在哪个线程上创建的?我在哪里可以找到这方面的阅读材料?
  • 我认为this 是我正在寻找并且应该阅读的内容。
  • @fuximusfoe:您可以为每个线程创建一个消息泵,因此不同的窗口在不同的线程上运行而不会相互阻塞,但这是一种不必要的复杂架构,其他人会觉得维护起来很困惑。见stackoverflow.com/questions/1566791/run-multiple-ui-threads
猜你喜欢
  • 1970-01-01
  • 2023-03-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多