簡體   English   中英

這是在數組或數據項列表中循環調用Dispatcher.BeginInvoke / lambda的正確方法嗎

[英]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中被適當捕獲。

http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM