C# 多线程调度程序
我有一个带有后台工作程序的多线程应用程序,我用它来显示一个启动屏幕,以处理主窗口的创建 我想在程序中更新线程“u”中的进度条,因此每次我想从线程“u”更新进度条时,都必须调用进度条控件。 所以这意味着我不必特别使用backgroundWorker\u DoWork 我遇到的问题是,调用backgroundWorker_RunWorkerCompleted事件时,无法显示主窗口form2 我想问题出在调度员身上C# 多线程调度程序,c#,wpf,multithreading,progress-bar,dispatcher,C#,Wpf,Multithreading,Progress Bar,Dispatcher,我有一个带有后台工作程序的多线程应用程序,我用它来显示一个启动屏幕,以处理主窗口的创建 我想在程序中更新线程“u”中的进度条,因此每次我想从线程“u”更新进度条时,都必须调用进度条控件。 所以这意味着我不必特别使用backgroundWorker\u DoWork 我遇到的问题是,调用backgroundWorker_RunWorkerCompleted事件时,无法显示主窗口form2 我想问题出在调度员身上 public partial class App : Application {
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窗口类上没有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.InvokeActiondelegate(即,使用拥有Form2对象的Dispatcher对象),那么不仅代码应该编译,调用this.Form2.Show也应该工作。请注意,不需要第二次调用Dispatcher.Run,实际上应该避免
因此,该方法的正确实现如下所示:
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
splashScreen.Close();
this.Form2.Dispatcher.Invoke((Action)delegate()
{
this.Form2.Show(); // works fine
});
}
现在,也就是说……在我看来,整个方法是有缺陷的。你真的应该在你的程序中有一个UI线程。如果希望在显示初始屏幕时首先发生某些事情,则将初始屏幕窗口设置为显示的第一个窗口,运行后台任务,然后在完成后在同一线程中显示主窗口
下面是一个您似乎正在尝试做的事情的示例,但编写时只使用主UI线程和正常的WPF启动机制
假设我们从VisualStudio中的一个普通WPF项目开始。这其中已经有一个应用程序类和一个主窗口类。我们只需要编辑它就可以了
首先,我们需要一个闪屏窗口。大部分配置可以在XAML中完成;因为您希望根据屏幕大小计算宽度和高度,所以我最容易将其放入构造函数中。结果是这样的:
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
splashScreen.Close();
this.Form2.Dispatcher.Invoke((Action)delegate()
{
this.Form2.Show(); // works fine
});
}
SplashScreenWindow.xaml:
现在,上面的类是我们希望首先显示的类。因此,我们通过更改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>
这里唯一棘手的事情是,在显示主窗口后,必须关闭启动屏幕窗口。否则,WPF会认为您已经关闭了最后一个窗口,从技术上讲,您会关闭该程序。通过在关闭闪屏窗口之前显示主窗口,程序将继续运行
在我看来,这是一种更好的方法,因为它与正常的WPF机制一起工作,而不是试图颠覆和/或绕过它们。它利用了程序启动时自动创建的调度程序,不需要额外的UI线程等。哦,而且……它可以工作。就是这样 我不能说这不是一个很好的错误描述。你试了什么?发生了什么事?很抱歉没有指定,当我试图显示Form2时,接口_进程中没有发生任何事情。主窗口未显示,但应用程序正在运行。错误太多。。。首先,后台工作线程将在主线程中完成,所以您不需要调用。System.Windows.Threading.Dispatcher.Run;-这是什么意思?这行可以删除,它不做任何事情。你不需要u线程来更新进度条。进度条必须从主威胁更新!我认为您还应该在runworkerCompleted上创建主窗口mw=新的主窗口;mw.show;谢谢你的建议,我必须保留“u”线程,因为任务必须由我的程序完成。我解决这个问题的方法是先显示我的主窗口,然后再关闭启动屏幕,看看你是如何说的,它是如何工作的。
<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>
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);
}
}
}