2010-12-16 92 views
4

我的表單上有我的自定義控件。跨線程操作無效,即使使用InvokeRequired

我在表格的方法:

private void SetEnabledOnControls(bool val) 
{ 
    if (InvokeRequired) 
    { 
     Invoke((Action<bool>)SetEnabledOnControls, val); 
    } 
    else 
    { 
     //do the work - iterate over child controls, 
     //and they iterate over their children, etc... 
    } 
} 

而這是在else分支我得到的提到例外的方法中:
Cross-thread operation not valid: Control 'txtNumber' accessed from a thread other than the thread it was created on.

我的情況實際上是更復雜一點 - 我只是推斷這個例子。實際上,我使用的是WorkflowFoundation - 我在WorkflowApplication(運行在它自己的線程中)中運行了StateMachineActivity(CTP1),我訂閱了它的事件,並從那裏打電話給SetEnabledOnControls。此外,我正在使用書籤來恢復我的工作流程(並且,還有一邊是MEF,並未涉及該場景)。

所有這一切都與我對InvokeRequired的顯而易見的誤解無關 - 如果InvokeRequired爲false,我有跨線程異常怎麼可能?我沒有手動創建任何控件 - 它們都在由設計者放置的Initialize()中。

任何人都可以對此有所瞭解嗎?

謝謝!

編輯 使用GWLlosa建議,我已經使用System.Threading.Thread.CurrentThread.ManagedThreadId跟蹤的ThreadId。現在出現了奇怪的部分... Initialize()中的線程ID爲10.在傳遞前兩個狀態之間,它帶有Id 13 - InvokeRequired爲true,並且它調用正確。但是,在第二個狀態之後,當它進入SetEnabledOnControls時它又是13,但這次InvokeRequired是錯誤的!怎麼來的!?後來,當然,它不能改變兒童控制(不奇怪)。難道是表單以某種方式改變了它生活的線程?

EDIT 2 現在,我有打電話:

if (IsHandleCreated) 
{ 
    Invoke((Action<bool>)SetEnabledOnControls, val); 
} 

,它有IsHandleCreated爲真,但還是失敗,什麼devSpeed pointed at

編輯3 捂臉 :)一說是恢復狀態的按鈕是在爲形式的第一CancelButton。當它從屬性中移除時,codebihind仍然具有DialogResult =取消它 - 所以我的表單確實正在關閉,當然它丟失了句柄,因此InvokeRequired沒有返回正確的信息,因此錯誤。

謝謝大家!我今天學到了一件新東西:)

回答

2

如果您在創建控件時(在Initialize()函數中)記錄線程ID並在嘗試觸摸它之前記錄線程ID,可能會使您的調試更容易。一般來說,我已經看到過這種情況,當你在某個線程上創建了控件而不是你期望的線程時。

+0

非常有幫助!查看我的更新以獲取結果... – veljkoz 2010-12-17 10:48:00

0

我只是碰到了類似的問題我自己,我在那裏有一個函數拆除,然後動態地將FlowLayoutPanel的內創建控件:

 public static void RenderEditorInstance(DataContext dataContext, object selectedItem, Form targetForm, Control targetControl, List<DynamicUserInterface.EditorControl> editorControls, EventHandler ComboBox_SelectedIndexChanged, EventHandler TextBoxControl_TextChanged, EventHandler CheckBox_CheckChanged, EventHandler NumericUpDown_ValueChanged, CheckedListControl.ItemChecked OnItemChecked, EventHandler dateTimePicker_ValueChanged, DynamicUserInterface.DuplicationValidationFailed liveLookupValidationFailed, DynamicUserInterface.PopulateComboBoxCallback populateComboBoxCallback) 
     {   if (targetForm.InvokeRequired) 
      { 
       InstanceRenderer renderer = new InstanceRenderer(RenderEditorInstance); 
       targetForm.Invoke(renderer, dataContext, selectedItem, targetForm, targetControl, editorControls, ComboBox_SelectedIndexChanged, TextBoxControl_TextChanged, CheckBox_CheckChanged, NumericUpDown_ValueChanged, OnItemChecked, dateTimePicker_ValueChanged, liveLookupValidationFailed, populateComboBoxCallback); 
      } 
      else 
      { 
       targetControl.Padding = new Padding(2); 
       targetControl.Controls.Clear(); 

       ...{other code doing stuff here } 
      } 
     } 

以及約12一個實例,其中正在使用此代碼,一個跨線程異常正在被提出。使用這段代碼的所有實例都是這樣編寫的,即使用'await'關鍵字來異步實現接口構建。

使用GWLlosa提出的建議,我寫了一個擴展方法來控制,以獲得OwningThread控制屬於:

public static Thread OwnerThread(this Control ctrl) 
    { 
     Thread activeThread = null; 

     if (ctrl.InvokeRequired) 
     { 
      activeThread = (Thread)ctrl.Invoke(new Func<Control, Thread>(OwnerThread), new object[] { ctrl }); 
     } 
     else 
     { 
      activeThread = Thread.CurrentThread; 
     } 

     return activeThread; 
    } 

..這強調,經過幾次反覆線程ID確實是不斷變化的。

裏面一些此代碼的深埋被例程用於獲取數據填充有關的控制,通過使用Task.Run(),其從MSDN(https://msdn.microsoft.com/en-us/library/hh195051(v=vs.110).aspx)明確規定的:

的例子顯示異步任務上比主應用程序線程

一旦Task.Run()被帶出方程的不同 線程執行,控制的線程從未改變。所以你需要小心使用它的方式和時間!