构造函数不在拥有该对象的线程上

时间:2012-07-10 05:18:24

标签: c# wpf multithreading invalidoperationexception

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

当我第一次点击按钮时,一切正常。然后我关闭了侧窗。现在当我点击按钮第二次时,它会抛出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类只是将MonitoringWindow类的相关方法包装在Dispatcher调用中,以处理跨线程调用。

代码输入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元素的所有调用来说,这是非常繁琐的,并且实际上并没有解决问题的原因。

非常感谢!

2 个答案:

答案 0 :(得分:2)

我猜测罪魁祸首是GraphUtilities.BackgroundGradient,但你没有列出GraphUtilities类。画笔是freezable个对象。

来自MSDN上的Freezable Objects Overview

  

冻结的Freezable也可以在线程之间共享,而解冻的Freezable则不能。[/ p>

因此,第一次运行时,该画笔与监视窗口线程相关联。下次打开该窗口时,它是一个新线程。如果要从其他线程中使用它,则必须在画笔上调用Freeze方法。

答案 1 :(得分:1)

WPF的前提条件是所有UI资源都由单个线程(UI线程)创建并拥有。许多事情取决于假设的有效性。如果你不遵守这一点,那么所有的赌注都会被取消。

您不需要在后台线程上创建等待指示符UI,以便在后台线程上使用它。大型应用程序在主线程上创建所有UI。后台线程上发生长时间运行的活动。到时候,控制marshalls进入UI线程的时间足够长,以便更新UI。

我建议您read this carefully继续操作。


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