【问题标题】:Window not closed properly when started from a thread从线程启动时窗口未正确关闭
【发布时间】:2017-08-13 07:17:37
【问题描述】:

我的 LoadingWindow 有问题。 我在 STA 模式下的新线程中启动一个新窗口。我知道这很不寻常,但对我来说是必需的。

问题是,在关闭窗口后 GC 并没有释放内存,并且窗口仍保留在可视活动树中:

我能做些什么来避免这种情况?

我的代码:

private void Button_Click(object sender, RoutedEventArgs e)
{
    if (_window == null)
    {
        Thread thread = new Thread(ShowWindow);
        thread.IsBackground = false;
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
    }
    else
    {
        _window.Dispatcher.Invoke(_window.Close);
        _window = null;
    }
}

private void ShowWindow()
{
    _window = new Window();
    _window.AllowsTransparency = true;
    _window.Background = new SolidColorBrush(Color.FromArgb(70,0,0,0));
    _window.Height = 1200D;
    _window.Width = 1920D;
    _window.WindowStartupLocation = WindowStartupLocation.Manual;
    _window.Left = -1920D;
    _window.Top = 0D;
    _window.WindowStyle = WindowStyle.None;

    _window.ShowDialog();
}

【问题讨论】:

  • 为什么你试图在不同的线程上打开一个窗口?绝对不需要。如果要执行后台处理,请使用任务执行并根据需要更新表单。如果您不希望表单是模态的,请不要将其打开为模态
  • 不幸的是这是必要的,因为主线程正在处理一些 ui 的东西(我们必须同步到服务器)并且需要 2-3 秒。需要加载屏幕来向用户显示客户端正在做某事。
  • 不,不是。启动画面不适用于单独的线程,它们是出现在其他表单之上的无模式表单。过去(即在.NET 4.0 之前)人们使用BackgroundWorker。现在他们使用async/await 和任务。与服务器同步并不意味着您也需要阻止 UI 线程。发布您的代码。需要解决的问题是您对服务器的阻塞等待
  • 你在说什么服务器,你如何与它通信?你现在是如何完成处理的? SOAP 代理、ADO.NET、WebClient 和 HttpClient 具有异步方法,因此您无需阻塞等待响应。任何其他通信方式都可以在后台运行Task.Run
  • 您在应用程序中看到的启动屏幕或进度屏幕不适用于不同的线程。他们在 UI 线程本身上工作。虽然处理是在后台线程中执行的,或者使用异步技术进行

标签: wpf multithreading user-interface


【解决方案1】:

您在应用程序上看到的启动画面不适用于单独的线程。它们是仍然在 UI 线程上运行的无模式表单(使用 Form.Show,而不是 Form.ShowDialog 打开)。任何后台工作都在后台线程上执行。对服务器的调用通常根本不使用线程。异步方法用于与 Web 服务、数据库等进行通信。

WCF 服务代理总是生成异步方法。直到 .NET 4.0,这些方法都遵循 APM 模式,即BeginMyMethod/EndMyMethod。从 .NET 4.5.2 开始,异步方法是基于任务的,即Task<SomeResult> MyMethodAsync()

鉴于新的异步方法,您的代码可以像这样简单:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    using(var progForm =new MyProgressForm())
    {
        progForm.Text="Running";
        progForm.Show(this);
        using(var client = new myServiceClient())
        {
            await client.MyMethodAsync();
        }
        progForm.Close();
}

就是这样。 UI 不会阻止等待MyMethodAsync 返回。 await 之后的代码只有在服务器完成后才会运行

APM 风格的方法可以通过TaskFactory.FromAsync 方法转换为任务。在TPL and Traditional .NET Framework Asynchronous Programming 中更详细地讨论了将 APM 样式或基于事件的界面转换为任务。

Task.FromAsync 与公开 AMP 方法的代理一起使用,代码可能如下所示:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    using(var progForm =new MyProgressForm())
    {
        progForm.Text="Running";
        progForm.Show(this);
        using(var client = new myServiceClient())
        {
            await Task.FromAsync(client.BeginMyMethod,client.EndMyMethod,null);
        }
        progForm.Close();
}

【讨论】:

  • 你是对的,这是更好的解决方案,但在我的情况下是不可能的。加载窗口时运行的方法会影响 MainWindow,所以我不能异步运行它。 LoadingWindow 不是飞溅,它更像是一个带有动画圆圈的窗口,用于显示加载进度。
【解决方案2】:

我知道这不是一个很好的解决方案......实际上它很糟糕。但就我而言,无法以其他方式修复。但是临时修复也没关系。 要完成此操作:我以这种方式更改了我的代码:

private void Button_Click(object sender, RoutedEventArgs e)
{
    if (_window == null)
    {
        Thread thread = new Thread(ShowWindow);
        thread.IsBackground = false;
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
    }
    else
    {
        _window.Dispatcher.Invoke(_window.Close);

        Window w = _window;
        _window = null;
        new Thread(() =>
        {
            Thread.Sleep(100);
            w.Dispatcher.InvokeShutdown();
            w = null;
        }).Start();
    }
}

private Window CreateWindow()
{
    Window w = new Window();
    w.AllowsTransparency = true;
    //w.Content = Some magic Animated Circle...
    w.Background = new SolidColorBrush(Color.FromArgb(70, 0, 0, 0));
    w.WindowStartupLocation = WindowStartupLocation.Manual;
    w.WindowStyle = WindowStyle.None;

    return w;
}

public void ShowWindow()
{
    Thread uiThread = new Thread(() =>
    {
        _window = CreateWindow();
        _window.Show();
        System.Windows.Threading.Dispatcher.Run();
    });

    uiThread.SetApartmentState(ApartmentState.STA);
    uiThread.IsBackground = true;
    uiThread.Start();
}

当我们的应用程序完全改变 wpf 我首先删除它:D 感谢@Panagiotis Kanavos 的积极反馈。在 99% 的情况下,您的权利;) 但在我的情况下,我无法执行异步方法......将来我们可以但现在不行。

【讨论】:

    【解决方案3】:

    是不是像这样的闪屏? splash on CBR

    【讨论】:

    • 这是一段不错的代码,我会用 Dispatcher 试试看 :)
    • @THeJ 没有。这是一段的代码。在 .NET 4+ 中,您不需要任何这些技巧。显示 splasGiven 最早支持的版本是 4.5.2,你根本不需要这个。正确的代码需要 4 行
    猜你喜欢
    • 2023-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-27
    • 2011-11-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多