繁体   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