多线程调度器

Dispatcher with multi thread

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

我想在我的程序中更新线程 'u' 中的进度条,因此每次我想从线程 'u' 更新进度条时都必须调用进度条控件。 所以这意味着我不必特别使用 "backgroundWorker_DoWork"。

我遇到的问题是调用 "backgroundWorker_RunWorkerCompleted" 事件时无法显示 mainwindow (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);
        }
    }
}

我不清楚您发布的代码如何编译,因为 WPF Window class 没有 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() — 即使用拥有 Form2 对象的 Dispatcher 对象 — 不仅应该代码编译,但调用 this.Form2.Show() 也应该有效。请注意,不需要第二次调用 Dispatcher.Run(),实际上应该避免。

该方法的 "correct" 实现如下所示:

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


现在,就是说……在我看来,整个方法都是有缺陷的。你真的应该在你的程序中只有一个 UI 线程。如果您希望在显示初始屏幕时先发生某些事情,那么让初始屏幕 window 成为第一个显示的 window,运行 后台任务,然后显示主要 window 完成后在同一个线程中。

这是一个您似乎正在尝试做的事情的示例,但编写时仅使用主 UI 线程和正常的 WPF 启动机制…

假设我们从 Visual Studio 中的普通 WPF 项目开始。这里面已经有一个 App class 和一个 MainWindow class。我们只需要编辑它来做你想做的事。

首先,我们需要一个启动画面window。大部分配置都可以在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;
    }
}

现在上面的class就是我们要先显示的那个。所以我们编辑 App class 的 XAML 来做到这一点,通过改变 StartupUri 属性:

<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 class 到 运行 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);
        }
    }
}

其中唯一棘手的事情是启动画面 window 必须在 显示主 window 之后关闭 。否则,WPF 会认为您已经关闭了最后一个 window(好吧,从技术上讲,您会关闭 :) )并将关闭该程序。通过在关闭初始屏幕 window 之前显示主要 window,程序继续 运行。

在我看来,这是一种很多更好的做事方式,因为它正常的 WPF 机制一起工作,而不是试图颠覆 and/or 绕过它们。它利用了程序启动时自动创建的 Dispatcher,不需要额外的 UI 线程等。哦,还有……它 有效 .就是这样。 :)