0

单击主窗口上的按钮时,我会打开一个侧窗口。此侧窗口是在与主窗口不同的线程上创建的。

当我第一次单击按钮时,一切正常。然后我关闭侧窗。现在,当我第二次单击该按钮时,它会抛出InvalidOperationException.

当侧窗关闭时,它和它运行的线程都被丢弃,每次打开时,都会创建一个全新的线程,从而创建一个全新的窗口。在窗口的最低子元素的构造函数中引发异常(即在构造窗口时第一次访问 UI 对象)。

这就是发生的事情。按钮单击的处理程序:

public partial class MainWindow : Window
{
    private void OnVariableMonitoringButtonClick(object sender, RoutedEventArgs e)
    {
        if (monitoringWindow == null)
        {
            Cursor = Cursors.Wait;
            monitoringWindow = new MonitoringWindowWrapper();
            monitoringWindow.Loaded += OnMonitoringWindowLoaded;
            monitoringWindow.Closed += OnMonitoringWindowClosed;
            monitoringWindow.Start(); // <---
        }
        else
        {
            monitoringWindow.Activate();
        }
        e.Handled = true;
    }

    void OnMonitoringWindowLoaded(object sender, EventArgs e)
    {
        Dispatcher.Invoke(new Action(() => Cursor = Cursors.Arrow));
    }

    void OnMonitoringWindowClosed(object sender, EventArgs e)
    {
        monitoringWindow = null;
        Dispatcher.Invoke(new Action(() => Cursor = Cursors.Arrow));
    }
}

该类MonitoringWindowWrapper仅将类的相关方法包装MonitoringWindowDispatcher调用中以处理跨线程调用。

代码进入MonitoringWindowWrapper.Start()方法:

partial class MonitoringWindowWrapper
{
    public void Start()
    {
        // Construct the window in a parallel thread
        Thread windowThread = new Thread(LoadMonitoringWindow);
        windowThread.Name = "Monitoring Window Thread";
        windowThread.IsBackground = true;
        windowThread.SetApartmentState(ApartmentState.STA);
        windowThread.Start(); // <---
    }

    private void LoadMonitoringWindow()
    {
        try
        {
            // Construct and set up the window
            monitoringWindow = new MonitoringWindow(); // <---
            monitoringWindow.Loaded += OnMonitoringWindowLoaded;
            monitoringWindow.Closed += OnMonitoringWindowClosed;
            monitoringWindow.Show();

            // Start window message pump on this thread
            System.Windows.Threading.Dispatcher.Run();
        }
        catch (Exception e)
        {
            // Catch any exceptions in this window to save the application from crashing
            ErrorMessasgeBox.Show(e.Message);
            if (Closed != null) Closed(this, new EventArgs());
        }
    }
}

然后代码进入MonitoringWindow.MonitoringWindow()构造函数并过滤到窗口中最低的子元素:

public partial class MonitoringWindow : Window
{
    public MonitoringWindow()
    {
        InitializeComponent(); // <---
        // ...
    }
}

一直到:

public partial class LineGraph : UserControl, IGraph, ISubGraph
{

    public LineGraph()
    {
        InitializeComponent();
        Brush b = GraphUtilities.BackgroundGradient;
        Background = b; // EXCEPTION THROWN AT THIS LINE
        BorderBrush = GraphUtilities.Border;
    }
}

异常调用堆栈可以让我们深入了解抛出异常的位置:

System.InvalidOperationException was unhandled by user code
 HResult=-2146233079
 Message=The calling thread cannot access this object because a different thread owns it.
 Source=WindowsBase
 StackTrace:
    at System.Windows.Threading.Dispatcher.VerifyAccess()
    at System.Windows.Freezable.ReadPreamble()
    at System.Windows.Media.GradientStopCollection.OnInheritanceContextChangedCore(EventArgs args)
    at System.Windows.DependencyObject.OnInheritanceContextChanged(EventArgs args)
    at System.Windows.DependencyObject.OnInheritanceContextChanged(EventArgs args)
    at System.Windows.Freezable.AddInheritanceContext(DependencyObject context, DependencyProperty property)
    at System.Windows.DependencyObject.ProvideSelfAsInheritanceContext(DependencyObject doValue, DependencyProperty dp)
    at System.Windows.DependencyObject.ProvideSelfAsInheritanceContext(Object value, DependencyProperty dp)
    at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType)
    at System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType, Boolean isInternal)
    at System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)
    at System.Windows.Controls.Control.set_Background(Brush value)
    at Graphing.LineGraph..ctor() in z:\Documents\Projects\Software\Serial\SerialWindows\Graphing\LineGraph\LineGraph.xaml.cs:line 28
    at System.RuntimeType.CreateInstanceDefaultCtor(Boolean publicOnly, Boolean skipCheckThis, Boolean fillCache, StackCrawlMark& stackMark)

我能想到的唯一原因如下:
1. 在另一个线程上创建的对象的Background属性绑定到该LineGraph对象的属性?但是,我的应用程序中没有任何这样的绑定(至少是明确的),如果是这种情况,那么为什么它第一次工作?
2. 该Background属性不知何故不归LineGraph对象所有?但这没有任何意义。
3. 构造函数没有在创建它正在构造的对象的线程上执行?这又没有任何意义,Visual Studio 调试器说它正在“监视窗口线程”线程上运行。

我该如何解决这个问题?

我可以使用Dispatcher来设置背景,但这对于该类和所有其他类中的所有 UI 元素的调用来说都是非常乏味的,而且它实际上并没有解决问题的原因。

非常感谢你!

4

2 回答 2

2

我猜罪魁祸首是GraphUtilities.BackgroundGradient,但您没有列出该GraphUtilities课程。画笔是可冻结的对象。

来自MSDN 上的可冻结对象概述:

冻结的 Freezable 也可以跨线程共享,而未冻结的 Freezable 则不能。

所以第一次运行时,那个刷子是和监控窗口线程相关联的。下次打开该窗口时,它是一个新线程。如果您想从其他线程中使用它,则必须在画笔上调用 Freeze 方法。

于 2012-07-11T04:21:31.473 回答
1

WPF 的先决条件是所有 UI 资源都由单个线程(即 UI 线程)创建并拥有。许多事情都取决于假设的有效性,即这将是这样。如果您不遵守此规定,那么所有赌注都将取消。

您无需在后台线程上创建等待指示器 UI 即可在后台线程上使用它。大型应用程序在主线程上创建所有 UI。长时间运行的活动发生在后台线程上。时机成熟时,控件编组到 UI 线程上的时间足以更新 UI。

我建议您在继续之前仔细阅读此内容。


所以你得到了你想要的答案。可以做某事的事实并不意味着它是一个好主意。在软件设计中,聪明很少与聪明相同。

于 2012-07-10T05:36:30.173 回答