C#中的多线程启动画面?

时间:2020-03-05 18:49:05  来源:igfitidea点击:

我希望在加载应用程序时显示启动屏幕。我有一个与系统托盘控件绑定在一起的表单。我希望在加载此表单时显示启动屏幕,这需要一些时间,因为它正在访问Web服务API来填充一些下拉菜单。我还想在加载之前对依赖项进行一些基本测试(即,Web服务可用,配置文件可读)。随着启动的每个阶段的进行,我想用进度更新初始屏幕。

我已经阅读了很多有关线程的文章,但是我对应该通过(main()方法吗?)控制它的位置迷失了方向。我也缺少Application.Run()的工作方式,这是应该从中创建线程的地方吗?现在,如果带有系统任务栏控件的表单是"活动"表单,那么飞溅应该从那里来吗?反正要等到表格完成才加载吗?

我不是在寻找代码讲义,更多的是算法/方法,所以我可以一劳永逸地解决这个问题:)

解决方案

回答

一种简单的方法是将以下内容用作main():

<STAThread()> Public Shared Sub Main()

    splash = New frmSplash
    splash.Show()

    ' Your startup code goes here...

    UpdateSplashAndLogMessage("Startup part 1 done...")

    ' ... and more as needed...

    splash.Hide()
    Application.Run(myMainForm)
End Sub

当.NET CLR启动应用程序时,它将创建一个"主"线程并开始在该线程上执行main()。最后,Application.Run(myMainForm)做两件事:

  • 使用已经执行main()的线程作为GUI线程,启动Windows"消息泵"。
  • 将"主表单"指定为应用程序的"关机表单"。如果用户关闭该窗体,则Application.Run()终止,控制权返回到main(),我们可以在其中执行所需的任何关闭操作。

不需要产生线程来照顾启动窗口,实际上这是一个坏主意,因为那样的话,我们将必须使用线程安全的技术来从main()更新启动内容。

如果需要其他线程在应用程序中执行后台操作,则可以从main()中生成它们。只需记住将Thread.IsBackground设置为True,这样它们将在主/ GUI线程终止时消失。否则,我们将不得不安排自己终止所有其他线程,否则当主线程终止时,它们将使应用程序保持活动状态(但没有GUI)。

回答

诀窍是创建负责启动屏幕显示的单独线程。
在运行时,应用程序.net将创建主线程并加载指定的(主)表单。要隐藏艰苦的工作,我们可以隐藏主窗体,直到完成加载。

假设Form1是主要表单,而SplashForm是顶级表单,则边界不错的初始表单:

private void Form1_Load(object sender, EventArgs e)
{
    Hide();
    bool done = false;
    ThreadPool.QueueUserWorkItem((x) =>
    {
        using (var splashForm = new SplashForm())
        {
            splashForm.Show();
            while (!done)
                Application.DoEvents();
            splashForm.Close();
        }
    });

    Thread.Sleep(3000); // Emulate hardwork
    done = true;
    Show();
}

回答

好吧,对于我过去部署的ClickOnce应用程序,我们使用Microsoft.VisualBasic命名空间来处理启动屏幕线程。我们可以引用和使用Cin .NET 2.0中的Microsoft.VisualBasic程序集,它提供了很多不错的服务。

  • 具有从Microsoft.VisualBasic.WindowsFormsApplicationBase继承的主窗体
  • 覆盖" OnCreateSplashScreen"方法,如下所示:
protected override void OnCreateSplashScreen()
{
    this.SplashScreen = new SplashForm();
    this.SplashScreen.TopMost = true;
}

非常简单,它会在加载过程中显示SplashForm(我们需要创建),然后在主窗体完成加载后自动将其关闭。

这确实使事情变得简单,并且VisualBasic.WindowsFormsApplicationBase当然已通过Microsoft的严格测试,并且具有许多功能,即使在100%C#的应用程序中,也可以使我们在Winforms中的工作变得更加轻松。

归根结底,所有这些都是IL和字节码,那么为什么不使用它呢?

回答

我认为使用aku或者Guy这类方法是可行的方法,但是从特定示例中可以去除以下几点:

  • 基本前提是尽快将启动显示在单独的线程上。这就是我学习的方式,类似于aku所说明的那样,因为这是我最熟悉的方式。我不知道提到的VB功能。而且,甚至认为这是一个VB库,他是对的-最后都是IL。因此,即使感觉很脏也不是那么糟糕! :)我想我们将要确保VB为该覆盖提供单独的线程,或者我们自己创建一个线程-一定要对此进行研究。
  • 假设我们创建另一个线程来显示此启动画面,则需要注意跨线程UI更新。我提到这一点是因为我们提到了更新进度。基本上,为了安全起见,我们需要使用委托在启动表单上调用一个更新函数(由我们创建)。我们可以将该委托传递给初始屏幕的表单对象上的Invoke函数。实际上,如果直接调用启动表单以更新其上的进度/ UI元素,则将在运行.Net 2.0 CLR的情况下出现异常。根据经验,表单上的任何UI元素都必须由创建它的线程进行更新-这就是Form.Invoke所保证的。

最后,我可能会选择在代码的main方法中创建启动文件(如果不使用VB重载)。对我来说,这比让主窗体执行对象的创建并将其紧密绑定更好。如果采用这种方法,建议我们创建一个由初始屏幕实现的简单接口,例如IStartupProgressListener之类,该接口通过成员函数接收启动进度更新。这将使我们可以根据需要轻松地换入/换出任何一个类,并很好地解耦代码。如果我们在启动完成时发出通知,启动表也可以知道何时关闭自身。

回答

我建议在aku提供的答案中,在最后一个" Show();"之后直接调用" Activate();"。

引用MSDN:

Activating a form brings it to the
  front if this is the active
  application, or it flashes the window
  caption if this is not the active
  application. The form must be visible
  for this method to have any effect.

如果我们不激活主窗体,则该窗体可能会显示在其他任何打开的窗口后面,从而显得有些愚蠢。

回答

我在codeproject的应用程序中发布了有关启动画面合并的文章。它是多线程的,可能是我们感兴趣的

C#中的另一个启动画面

回答

这是一个古老的问题,但是在尝试为WPF查找可能包含动画的线程启动画面解决方案时,我一直遇到这个问题。

这是我最终拼凑而成的内容:

App.XAML:

<Application Startup="ApplicationStart" …

App.XAML.cs:

void ApplicationStart(object sender, StartupEventArgs e)
{
        var thread = new Thread(() =>
        {
            Dispatcher.CurrentDispatcher.BeginInvoke ((Action)(() => new MySplashForm().Show()));
            Dispatcher.Run();
        });
        thread.SetApartmentState(ApartmentState.STA);
        thread.IsBackground = true;
        thread.Start();

        // call synchronous configuration process
        // and declare/get reference to "main form"

        thread.Abort();

        mainForm.Show();
        mainForm.Activate();
  }