2012-07-10 155 views
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類只是包裝了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

我猜猜罪魁禍首是GraphUtilities.BackgroundGradient,但你還沒有列出GraphUtilities類。畫筆是freezable對象。

Freezable Objects Overview上MSDN:

的冷凍可凍結還可在線程間共享,同時未凍結的Freezable不能。

因此,您第一次運行時,該畫筆與監視窗口線程關聯。下次打開該窗口時,這是一個新線程。如果你想從另一個線程使用它,你將不得不調用畫筆上的Freeze方法。

+0

謝謝!我向'GraphUtilities'類添加了一個'static'構造函數,它在所有暴露的'Brush'對象上調用'Freeze()'。這解決了問題!謝謝 :) – Brett 2012-07-11 20:54:52

1

這是WPF的一個先決條件,即所有UI資源都由單個線程(UI線程)創建並擁有。許多事情取決於假設的有效性,這將是如此。如果你不遵守這個規定,那麼所有的投注都會關閉。

你不需要爲了營造在後臺線程等待指示用戶界面在後臺線程使用它。大型應用程序在主線程上創建所有的UI。後臺線程上發生長時間運行的活動。當時機到來時,將控件封送到UI線程足夠長的時間以更新UI。

在繼續之前,我建議你read this carefully


所以你得到了你想要的答案。事情可以做不成爲一個好主意。在軟件設計中,聰明很少和聰明一樣。

+0

我的理解是,UI資源可以存在於任何線程上,只要它處於STA模式。唯一的其他條件是它們只能由創建它的線程訪問。例如,請閱讀此處:http://blogs.msdn.com/b/dwayneneed/archive/2007/04/26/multithreaded-ui-hostvisual.aspx。單獨線程的主要原因是避免在加載側窗時阻塞主UI,並在等待時向用戶顯示忙碌光標。這在大型應用程序中似乎很常見,所以它一定是可能的。謝謝。 – Brett 2012-07-10 10:59:28