在单独的线程中加载屏幕出现问题

5
我有一个旧的Windows Forms应用程序,在许多地方对数据库进行一些搜索。有时候它需要很长时间,所以我决定在wpf中创建一个加载屏幕,以向用户显示在单独的线程中正在加载某些内容。基本上只是一个带有加载指示器(一个旋转的圆圈)的完全透明的窗口。在我的主机计算机和虚拟机上一切正常,但当我尝试部署到我们的演示环境时,就像它开始加载,指示器出现,几秒钟后它消失了,应用程序停止响应,好像永远无法恢复。我最初的想法是它与GPU加速有关,它无法处理透明度,但它会显示几秒钟,所以这不可能是问题。所以很可能是我做错了什么。下面您可以看到我的代码,您是否注意到任何可能出错/导致死锁或其他问题的地方?
  public class LoadingManager
  {
    public LoadingManager()
    { }
    public LoadingManager(string LoadingText)
    {
        loadingText = LoadingText;
    }

    private string loadingText = "Please wait ..";
    private Thread thread;
    private bool ThreadReadyToAbort = false;
    private BusyIndicatorView loadingWindow;

    public void BeginLoading()
    {
        this.thread = new Thread(this.RunThread);
        this.thread.IsBackground = true;
        this.thread.SetApartmentState(ApartmentState.STA);
        this.thread.Start();
    }

    public void EndLoading()
    {
        if (this.loadingWindow != null)
        {
            this.loadingWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
            { this.loadingWindow.Close(); }));
            while (!this.ThreadReadyToAbort) { }; // I also tried to remove this while but it didn't help
        }
        this.thread.Abort();
    }

    public void RunThread()
    {
        this.loadingWindow = new BusyIndicatorView();

        loadingWindow.tbLoadingCaption.Text = loadingText;
        this.loadingWindow.Closing += new System.ComponentModel.CancelEventHandler(waitingWindow_Closed);
        this.loadingWindow.ShowDialog();
    }

    void waitingWindow_Closed(object sender, System.ComponentModel.CancelEventArgs e)
    {
        Dispatcher.CurrentDispatcher.InvokeShutdown();
        this.ThreadReadyToAbort = true;
    }

编辑.

我注意到在这些机器上,通常(有时候第一次点击也会失败)只要我第一次点击搜索,它就可以正常工作。如果我再次点击,它会显示一秒钟然后消失,应用程序停止响应。因此,似乎线程没有被关闭,调度程序关闭失败了?但是没有抛出任何异常...


为什么要使用线程来显示窗口?实际的加载是否发生在主线程上? - H H
是的,加载在主线程中,我知道应该反过来,但这是一个旧的Windows Forms应用程序。重写它需要很多努力。 - MajkeloDev
是WinForms还是WPF?最好明确一下。 - H H
主要应用程序是在Win Forms中,加载屏幕是使用WPF编写的DLL。 - MajkeloDev
那么它可能也可以在WinForms和WPF之间的交互中。我有点怀疑WPF窗口似乎启动了自己的Dispatcher的方式。 - H H
你的线程不应该需要Abort(),我建议先将其移除。它会自行运行完毕并终止。 - H H
1个回答

2

在完成之前,可以多次调用您的BeginLoading方法,因此可能会创建多个wpf窗口。这将混淆各种引用。另外,请不要中止线程,让它自己决定。以下是两个更改:

public void BeginLoading()
{
    this.thread = new Thread(this.RunThread);
    this.thread.IsBackground = true;
    this.thread.SetApartmentState(ApartmentState.STA);
    this.thread.Start();
    while (this.loadingWindow == null) { } // <--- Add this line
}

public void EndLoading()
{
    if (this.loadingWindow != null)
    {
        this.loadingWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
        { this.loadingWindow.Close(); }));
        while (!this.ThreadReadyToAbort) { }; 
    }
    //this.thread.Abort(); // <-- Remove this line
}

此外,如果这只是为了繁忙屏幕而做的所有工作,我会说一定有比这更好、更安全的方法。对于学术目的来说,这是一个有趣的问题,但不是生产代码,特别是如果将来一些初级开发人员可能会搞乱它。

编辑:如果我减少重复调用BeginLoading和EndLoading之间的延迟,我的机器仍然会崩溃。这可能与WPF窗口异步关闭的方式有关。我删除了Closed事件处理程序,只使用了一个布尔标志来指示窗口是否“已加载”,并且我没有看到任何问题:

public class LoadingManager2
{
  public LoadingManager2()
  { }
  public LoadingManager2(string LoadingText)
  {
    loadingText = LoadingText;
  }

  private string loadingText = "Please wait ..";
  private Thread thread;
  private MyWindow loadingWindow;
  private bool isLoaded = false;

  public void BeginLoading()
  {
    this.thread = new Thread(this.RunThread);
    this.thread.IsBackground = true;
    this.thread.SetApartmentState(ApartmentState.STA);
    this.thread.Start();
    while (!this.isLoaded) { };
  }

  public void RunThread()
  {
    this.loadingWindow = new MyWindow();
    this.isLoaded = true;
    this.loadingWindow.ShowDialog();
  }

  public void EndLoading()
  {
    this.loadingWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
    { 
      this.loadingWindow.Close();
      this.isLoaded = false;
    }));
    while (this.isLoaded) { }; 
  }
}

有趣。我会在几个小时内尝试一下,然后告诉您结果。 - MajkeloDev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接