【问题标题】:Closing a window owned by a different thread关闭由不同线程拥有的窗口
【发布时间】:2011-12-19 22:14:27
【问题描述】:

我是线程新手。我在我的 WPF 应用程序中使用后台线程来与数据库通信和消息通信。

其中一个视图模型应打开一个单独的窗口。由于这应该作为 UI 线程运行,所以我正在做:

    private void OnSelection(SelectionType obj)
    {
        Thread thread = new Thread(ShowRegionWindow);
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
    }
    private void ShowRegionWindow()
    {
        var rWindow = new RegionWindow();
        rWindow .Show();
        rWindow .Closed += (s, e) => System.Windows.Threading.Dispatcher.ExitAllFrames();
        System.Windows.Threading.Dispatcher.Run();
    }

现在我需要在另一条消息上关闭此窗口。我该怎么做?

【问题讨论】:

  • 为什么它必须作为 UI 线程运行?如果只是创建一个昂贵的对象,您可以在背景踏板上创建该对象,然后将其绑定到主对象(很容易关闭)。
  • 它没有创建对象。它正在创建另一个 WPF 窗口
  • 您的问题陈述是“与数据库对话的后台线程”。如果您没有创建一个与数据库对话的对象,那么您对数据库做了什么?
  • 我的问题是没有用线程与数据库交谈。我已经通过后台线程这样做了。现在进行一些操作,后台线程需要打开一个新的 WPF 窗口并关闭。我怎么做。对不起,如果我的问题不清楚
  • 为什么后台线程确实需要打开窗口?您可以在从后台线程检索结果以打开新窗口的主线程上进行回调。什么操作需要新窗口?您可以重构以获取有关主要的信息并启动另一个背景吗?

标签: c# .net wpf multithreading mvvm-light


【解决方案1】:

在我继续之前,您说您是线程新手,我想强调一下,您的应用程序可能没有充分的理由在不同线程上打开窗口。您使用 MVVM 很好,但您可能做得不对。理想情况下,您的所有视图和视图模型都将位于主 UI 线程上。模型层中的任何工作线程都需要在与视图模型交互之前调用 UI 调度程序。例如,您可能有一个工作线程上的更新事件调用视图模型上的处理程序来更新 UI。 UI 调度程序应该在调用该事件之前或之后立即调用。 (但要清楚,模型不应该知道视图模型。)

事实上,您似乎在 UI 事件处理程序中创建了一个新窗口,这意味着您可能应该这样做:

private void OnSelection(SelectionType obj)
{
    var rWindow = new RegionWindow();
    rWindow.Show();
}

然而,也许你有完全正当的理由以你现在的方式做这件事。如果是这样,您可以从调用线程关闭该新窗口的一种方法是传入一个事件。你可以这样做:

private event Action CloseRegionWindows = delegate { }; // won't have to check for null

private void OnSelection(SelectionType obj)
{
    Thread thread = new Thread(() => ShowRegionWindow(ref CloseRegionWindows));
    ...
}

private void ShowRegionWindow(ref Action CloseRegionWindows)
{
    var rWindow = new RegionWindow();
    rWindow.Show();
    CloseRegionWindows += () => rWindow.Dispatcher.BeginInvoke(new ThreadStart(() => rWindow.Close()));
    ...
}

然后在某处引发该事件:

private void OnClick(object sender, RoutedEventArgs args)
{
    CloseRegionWindows();
}

【讨论】:

  • 这对我不起作用。代码运行时窗口被冻结。并在代码完成后解冻。主线程正在冻结新线程。该线程还需要thread.SetApartmentState(ApartmentState.STA);,否则它会崩溃。为防止冻结窗口,您需要添加System.Windows.Threading.Dispatcher.Run();,但如果添加,则无法再停止线程。
【解决方案2】:

再次阅读您的一些cmets后,我想我对场景有了更好的理解。这是您需要做的。

首先,确保您的一个 ViewModel 具有对需要打开和关闭窗口的模型的引用。实现这一点的一种方法是构造函数依赖注入。

public ViewModel(Model model) // or IModel
{
    ...

接下来,您需要在该 ViewModel 中捕获 UI 调度程序。最好的地方可能也是 ViewModel 构造函数。

private Dispatcher dispatcher;

public ViewModel(Model model)
{
    dispatcher = Dispatcher.CurrentDispatcher;
    ...

现在在你的模型中创建两个事件;一开一关。

class Model
{
    internal event Action OpenWindow = delegate { };
    internal event Action CloseWindow = delegate { };
    ...

并在您的 ViewModel 构造函数中订阅它们。

public ViewModel(Model model)
{
    dispatcher = Dispatcher.CurrentDispatcher;
    model.OpenWindow += OnWindowOpen;
    model.CloseWindow += OnWindowClose;
    ...
}

现在使用 ViewModel 类中的 UI Dispatcher 打开和关闭窗口;

private Window window;

private void OnWindowOpen()
{
    // still on background thread here

    dispatcher.BeginInvoke(new ThreadStart(() =>
    {
        // now we're on the UI thread

        window = new Window();
        window.Show();
    }
}

private void OnWindowClose()
{
    dispatcher.BeginInvoke(new ThreadStart(() =>
    {
        window.Close();
    }
}

最后,从模型中的后台线程引发 OpenWindow 和 CloseWindow 事件,就像引发任何事件一样。您的模型可能如下所示:

class Model
{
    private Thread worker;

    internal event Action OpenWindow = delegate { };
    internal event Action CloseWindow = delegate { };

    public Model()
    {
        worker = new Thread(Work);
        worker.Start();
    }

    private void Work()
    {
        while(true)
        {
            if (/*whatever*/) OpenWindow();
            else if (/*whatever*/) CloseWindow();
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多