多线程调度器
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 线程等。哦,还有……它 有效 .就是这样。 :)
我有一个带后台工作程序的多线程应用程序,我用它来显示启动画面以处理主程序的创建 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 线程等。哦,还有……它 有效 .就是这样。 :)