[英]Is this the correct way to call Dispatcher.BeginInvoke/lambda in a loop over an array or list of data items
我繼承了一些代碼,這些代碼使用BeginInkoke將選項卡添加到TabControl中,如下所示:
foreach (DitaNestedContent content in root.Content)
{
CrlList.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action<TabControl>((tabControl) =>
{
TabItem aTab = new TabItem();
if (content.Paths != null)
{
PublicationsListUserControl crlTree = new PublicationsListUserControl(content.Path, filename);
crlTree.MinWidth = 5;
aTab.Content = crlTree;
}
aTab.Header = content.Name;
tabControl.Items.Add(aTab);
}), CrlList);
}
這一直有效,直到我重建項目后,仍創建了正確數量的選項卡,但每個選項卡都包含最后一個選項卡的內容(僅)。 我認為時間已更改,並且先前的代碼只是偶然地工作,並且第一個BeginInvoke現在僅在循環完成之后才啟動,因此, content
在運行時等於最后一個值。
因此,我決定重寫代碼,但對於最終似乎可行的方法感到驚訝:
List<String> contentPaths = new List<string>();
foreach (DitaNestedContent content in root.Content)
{
contentPaths.Add(String.Copy(content.Path));
}
for (Int32 i = 0; i < root.Content.Count; ++i)
{
CrlList.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action<TabControl>((tabControl) =>
{
if (i >= root.Content.Count) { i = 0; }
TabItem aTab = new TabItem();
if (contentPaths[i] != null)
{
String contentPath = contentPaths[i];
PublicationsListUserControl crlTree = new PublicationsListUserControl(contentPath, filename);
crlTree.MinWidth = 5;
aTab.Content = crlTree;
}
aTab.Header = root.Content[i].Name;
tabControl.Items.Add(aTab);
++i;
}), CrlList);
}
基本上,不是使用當前content
來調用PublicationsListUserControl
構造函數,而是在lambda中使用i
來重新計算應該使用哪個root.Content。
我本來以為(以及在我之前寫過代碼的人顯然認為),所使用的變量的值應該是在創建lambda時計算出來並存儲以供lambda使用的,而不是在BeginInvoke開始工作時才使用。
在循環中可靠地使用帶有lambda的BeginInvoke正確嗎? 還是我偏離標准?
更新
foreach變量只能由C#5.0捕獲,請參見此處:
http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/ http://csharp.2000things.com/2014/09/19/1186-在lambda表達式中捕獲每個迭代變量/
關於root.Content
枚舉是否有些慢? 即檢索到的每個新值都需要一些時間? 如果沒有,那么實際上沒有充分的理由對枚舉中的每個項目都執行新的調用。 該代碼確實應該將循環放入被調用的方法中。 即:
CrlList.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
(TabControl tabControl) =>
{
foreach (DitaNestedContent content in root.Content)
{
TabItem aTab = new TabItem();
if (content.Paths != null)
{
PublicationsListUserControl crlTree =
new PublicationsListUserControl(content.Path, filename);
crlTree.MinWidth = 5;
aTab.Content = crlTree;
}
aTab.Header = content.Name;
tabControl.Items.Add(aTab);
}
}), CrlList);
如果確實有充分的理由在調用之外執行foreach
,那么您就需要更加努力地確定確切的原因,為什么原始代碼不符合您的要求。 您原來的理論是不正確的,並且新代碼比原始代碼的錯誤更殘破。
一foreach
循環變量基本上每個循環迭代重新創建的,所以它是安全的捕捉變量本身,而for
為整個回路形成循環變量一次,因此並不安全捕獲。 換句話說,您的理論認為content
變量由每個調用的方法實例共享。 它們各自獲得變量的私有副本,因此實際執行被調用的方法並不重要。
通過捕獲該變量並在調用的匿名方法中遞增該變量,您嘗試解決單個for
循環變量肯定可以在某些情況下起作用。 但是,您同時遇到了種族問題和線程安全問題,因為調用BeginInvoke()
方法的循環正在修改該變量,該變量也在執行被調用方法的UI線程中使用。 如果在循環完成之前執行一個或多個調用,則循環本身將增加變量以及被調用的方法,從而使您繞過某些索引,重復其他索引,最糟糕的是,可能擁有索引在處理單個元素的中間更改權利。
解決在for
循環中進行捕獲的通常方法是擁有一個局部變量,將循環變量復制到該局部變量中,並使用該變量而不是循環變量。 例如:
for (int i = 0; i < max; i++)
{
int localIndex = i;
Dispatcher.Current.BeginInvoke(() => /* do something with localIndex, not i */);
}
但是,您實際上不需要使用for
循環。 正如我所說,在涉及變量捕獲時, foreach
特別安全,因此,無論您看到什么錯誤行為,它都不是捕獲錯誤。
(或者,如果是的話,您發布的代碼不是實際執行的代碼。請考慮發布一個好的代碼示例 ,該示例完整,簡潔並且可靠地再現了您遇到的問題)。
這里可能發生的情況是,您從代碼作者那里移走了一個編譯器版本,在C#5之前,foreach主體中定義的變量(您的內容變量)是在循環外部定義的,並且取了最后一個適用的值,在C#5之后,該變量在循環內部定義,並在lambda中被適當捕獲。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.