由于竞争条件,窗户关闭不起作用

时间:2014-11-26 08:46:30

标签: c# wpf multithreading race-condition

这是我的代码:

private void OpenLoadingWindow()
{
    loadingWindow = new LoadingView();
    loadingWindow.Closed += new EventHandler(LoadingWindow_Closed);

    _go = true;
    loadingWindow.ShowDialog();
}

public void OpenLoadingWindowInNewThread()
{
    thread = new Thread(x => OpenLoadingWindow());
    thread.IsBackground = true;
    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();

    lock (_locker)                 
    {                              
        Monitor.Pulse(_locker);
    }
}

public void CloseLoadingWindow()
{
    lock (_locker)
        while (!_go)
            Monitor.Wait (_locker);

    if (loadingWindow != null)
    {
        loadingWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, (Action)(() =>
        {
            _go = false;
            loadingWindow.Close();
            loadingWindow = null;
        }));
    }
}

在代码中我首先调用OpenLoadingWindowInNewThread()然后调用CloseLoadingWindow()。但是,第一次执行代码时它工作正常。但在那之后,BeginInvoke中的CloseLoadingWindow()中的代码不会被执行。我做错了什么?

我想要实现的是:打开加载窗口,执行一些代码。代码执行后我调用了闭包方法,我想关闭加载窗口。

1 个答案:

答案 0 :(得分:2)

这里的主要问题是您正在为UI创建第二个线程。别这么做。

不幸的是,你没有提供一个好的代码示例。所以为了回答,让我们假设您正在做这样的事情:

void button1_Click(object sender, EventArgs e)
{
    DoLoadingWork();
}

void DoLoadingWork()
{
    OpenLoadingWindowInNewThread();
    LoadingWork();
    CloseLoadingWindow();
}

即。某些事件发生在您的用户界面中,现在您必须做一些工作。您通过调用问题中显示的方法,处理UI线程中的工作并创建第二个线程来显示对话框来实现此目的。

这是解决这个问题的错误方法。相反,您应该将所有UI保留在同一个线程中,并在另一个线程中完成工作。这看起来更像是这样:

void DoLoadingWork()
{
    using (LoadingView form = new LoadingView())
    {
        form.Shown += async (sender, e) =>
        {
            await Task.Run(() => LoadingWork());
            form.Close();
        };

        form.ShowDialog();
    }
}

此版本执行以下操作:

  1. 在UI线程中创建状态对话框
  2. 订阅Shown事件,以确保在发生任何其他事件之前对话框可见
  3. 显示对话框
  4. 显示对话框后,将启动一个新线程以执行LoadingWork()方法
  5. LoadingWork()方法完成时,对话框将关闭,允许处理对话框并返回DoLoadingWork()方法。
  6. 请注意,即使您必须从执行处理的代码与UI进行交互,或者您需要一种方法来中断处理,上述操作仍然是正确的方法。可以使用标准惯用法来轻松实现这些要求的其他方面。

    如果没有关于该处理可能是什么的实际示例,以及UI交互和/或中断如何工作,则无法确切地说明该部分将如何实现。但它通常涉及使用Invoke()进行UI交互(甚至更好,重构处理以使其使用async / await,在await语句之间进行UI交互工作的各个部分)和一个标志或CancellationToken来处理中断线程。

    如果您的处理实际上与UI进行了交互,并且实际上您确实在UI线程中运行了它,那么您可能已经调用了Refresh()或{等方法{1}}穿插。实际上从不需要这些方法,并且恕我直言总是表示代码已被错误地实现。更改实施以将正确的代码放入正确的线程中的额外好处是,您不必使用任何这些方法与UI进行交互(相反,您将使用Application.DoEvents() )。