多线程的调度程序

时间:2015-04-19 12:24:07

标签: c# wpf multithreading progress-bar dispatcher

我有一个带有后台工作程序的多线程应用程序,我用它来显示一个启动画面来处理主窗口的创建。

我想更新帖子中的进度条' u'在我的程序中,所以每次我想从线程“u”中更新它时,我都必须调用进度条控件。 这意味着我不必使用" backgroundWorker_DoWork"尤其

我遇到的问题是,当" backgroundWorker_RunWorkerCompleted"时,我无法显示主窗口(form2)。事件被召唤。

我认为问题在于调度员。

public partial class App : Application
{
    public BackgroundWorker backgroundWorker;
    private SplashScreenWindow splashScreen;

    public static EventWaitHandle initWaitHandle = new AutoResetEvent(false);
    public MainWindow Form2 { get; set; } 
    [DllImport("kernel32.dll")]
    private static extern bool AllocConsole();

    protected override void OnStartup(StartupEventArgs e)
    {
        backgroundWorker = new BackgroundWorker();
        backgroundWorker.WorkerReportsProgress = true;
        backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
        backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);
        backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);

        splashScreen = new SplashScreenWindow();
        splashScreen.ShowInTaskbar = false;
        splashScreen.ResizeMode = ResizeMode.NoResize;
        splashScreen.WindowStyle = WindowStyle.None;
        splashScreen.Topmost = true;
        splashScreen.Width =  (SystemParameters.PrimaryScreenWidth) / 2.5;
        splashScreen.Height = (SystemParameters.PrimaryScreenHeight) / 2.5;
        splashScreen.Show();
        base.OnStartup(e);

        backgroundWorker.RunWorkerAsync();

        Thread u = new Thread(new ThreadStart(interface_process));
        u.SetApartmentState(ApartmentState.STA);
        u.Start();
    }

    public void interface_process()
    {
        MainWindow form2 = new MainWindow();
        this.Form2 = form2;
        System.Windows.Threading.Dispatcher.Run();
    }

    void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        splashScreen.Close();
        this.Form2.Invoke((Action)delegate()
        {
            this.Form2.Show(); // does not work
            System.Windows.Threading.Dispatcher.Run();
        });

    }

    void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        splashScreen.ValueProgressBar = e.ProgressPercentage;
    }

    void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        for (int i = 0; i <= 100; i += 10)
        {
            backgroundWorker.ReportProgress(i, "Chargement en cours : " + i);
            Thread.Sleep(500);
        }
    }
}

1 个答案:

答案 0 :(得分:0)

我不清楚你发布的代码是如何编译的,因为WPF Window类没有Invoke()方法。这将导致代码中的编译时错误:

void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    splashScreen.Close();
    this.Form2.Invoke((Action)delegate()
    {
        this.Form2.Show(); // does not work
        System.Windows.Threading.Dispatcher.Run();
    });
}

如果上面的代码被更改,以便方法中的第二个语句读取this.Form2.Dispatcher.Invoke((Action)delegate() - 也就是说,使用拥有Dispatcher对象的Form2对象 - 不仅应该是代码编译,但调用this.Form2.Show()也应该有效。请注意,不需要第二次调用Dispatcher.Run(),实际上应该避免。

&#34;正确&#34;因此,该方法的实现如下:

void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    splashScreen.Close();
    this.Form2.Dispatcher.Invoke((Action)delegate()
    {
        this.Form2.Show(); // works fine
    });
}


现在,那说......在我看来,整个方法都存在缺陷。你真的应该在你的程序中拥有一个UI线程。如果您希望在显示启动画面时首先发生某些事情,请将启动画面窗口设置为显示的第一个窗口,运行后台任务,然后在同一个主题窗口中显示主窗口。已完成。

以下是您似乎要尝试做的一个示例,但编写的仅使用主UI线程和正常的WPF启动机制......

假设我们从Visual Studio中的普通WPF项目开始。这将包含App类和MainWindow类。我们只需要编辑它就可以做你想做的事。

首先,我们需要一个闪屏窗口。大多数配置都可以在XAML中完成;因为你想要根据屏幕大小计算宽度和高度,所以将它放在构造函数中是最简单的(对我来说)。这看起来像这样:

<强> SplashScreenWindow.xaml:

<Window x:Class="TestSingleThreadSplashScreen.SplashScreenWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        DataContext="{Binding RelativeSource={RelativeSource Self}}"
        ShowInTaskbar="False"
        ResizeMode="NoResize"
        WindowStyle="None"
        Topmost="True"
        Title="SplashScreenWindow">
  <Grid>
    <ProgressBar HorizontalAlignment="Left" Height="10" Margin="10,10,0,0" 
                 VerticalAlignment="Top" Width="100"
                 Value="{Binding ValueProgressBar}"/>
  </Grid>
</Window>

<强> SplashScreenWindow.xaml.cs:

public partial class SplashScreenWindow : Window
{
    public readonly static DependencyProperty ValueProgressBarProperty = DependencyProperty.Register(
        "ValueProgressBar", typeof(double), typeof(SplashScreenWindow));

    public double ValueProgressBar
    {
        get { return (double)GetValue(ValueProgressBarProperty); }
        set { SetValue(ValueProgressBarProperty, value); }
    }

    public SplashScreenWindow()
    {
        InitializeComponent();

        Width = SystemParameters.PrimaryScreenWidth / 2.5;
        Height = SystemParameters.PrimaryScreenHeight / 2.5;
    }
}

现在上面的类是我们想要首先显示的类。因此,我们通过更改App属性来编辑StartupUri类的XAML来执行此操作:

<Application x:Class="TestSingleThreadSplashScreen.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="SplashScreenWindow.xaml">
    <Application.Resources>

    </Application.Resources>
</Application>

最后,我们需要App课程来运行BackgroundWorker,在不同时间执行相应的操作:

public partial class App : Application
{
    private SplashScreenWindow SplashScreen { get { return (SplashScreenWindow)this.MainWindow; } }

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        BackgroundWorker backgroundWorker = new BackgroundWorker();

        backgroundWorker.WorkerReportsProgress = true;
        backgroundWorker.DoWork += backgroundWorker_DoWork;
        backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
        backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
        backgroundWorker.RunWorkerAsync();
    }

    void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        new MainWindow().Show();
        SplashScreen.Close();
    }

    void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        SplashScreen.ValueProgressBar = e.ProgressPercentage;
    }

    void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker backgroundWorker = (BackgroundWorker)sender;

        for (int i = 0; i <= 100; i += 10)
        {
            backgroundWorker.ReportProgress(i, "Chargement en cours : " + i);
            Thread.Sleep(500);
        }
    }
}

唯一棘手的问题是,在显示主窗口后,必须关闭启动画面窗口。否则,WPF会认为你关闭了最后一个窗口(好吧,技术上你会有:))并将关闭程序。通过在关闭启动画面窗口之前显示主窗口,程序继续运行。

在我看来,这是很多更好的做事方式,因为它正常的WPF机制一起工作,而不是试图颠覆和/或解决他们。它利用了程序启动时自动创建的Dispatcher,不需要额外的UI线程等等。哦,而且...... 工作。所以就是这样。 :)