线程:不正确的变量传递C#

时间:2012-05-21 23:18:27

标签: c# multithreading variables

用户。我遇到了一个我无法找到答案的问题。我是Threading的新手(在C#中),遇到了这个问题。我有这个带有效果的图像编辑器,但由于它运行得太慢,我试图将其拆分为线程。问题是他总是使用效果列表中的最后一项运行“CreatePreview”命令。因此,如果我激活效果:“黑/白”,“Sature”和“GreenFilter”,它将尝试使用greenfilter创建3个预览。

有人可以帮我解决这个问题吗?

private void CreatePreviews(string fileName, List<IEffect> effects)
{
    List<Task> tasks = new List<Task>();
    foreach (var effect in effects)
    {
        //previews.Add(effect, CreatePreview(fileName, effect));
        Task task = new Task(delegate()
        {
            string result = CreatePreview(fileName, effect);
            Dispatcher.BeginInvoke(new Action(
            delegate()
            {
                ShowPreview(result, effect.DisplayName);
            }));

        });
        task.Start();
    }
}

4 个答案:

答案 0 :(得分:5)

我现在无法测试,但我很确定您的问题是closing over the loop variable

获取你的循环变量的副本并改为关闭它:

foreach (var effect in effects)
{
    var effectCopy = effect;

    //previews.Add(effectCopy, CreatePreview(fileName, effectCopy));
    Task task = new Task(delegate()
    {
        string result = CreatePreview(fileName, effectCopy);
        Dispatcher.BeginInvoke(new Action(delegate()
        {
            ShowPreview(result, effectCopy.DisplayName);
        }));
    });

    task.Start();
}

(或者等待C#5,它会在每次迭代时自动关闭变量的新副本。)

答案 1 :(得分:1)

您必须将当前effect保存到循环内的变量中,以防止访问委托中已修改的闭包,这意味着所有委托都访问循环变量,该变量最终具有循环的最后一个元素,因此所有任务都以最后一个效果运行。为了防止这种情况:

private void CreatePreviews(string fileName, List<IEffect> effects)
{
    List<Task> tasks = new List<Task>();

    foreach (var effect in effects)
    {
        var mcEffect = effect;

        Task task = new Task(delegate()
            {
                string result = CreatePreview(fileName, mcEffect);
                Dispatcher.BeginInvoke(new Action(
                delegate()
                {
                    ShowPreview(result, effect.DisplayName);
                }));
            });

        task.Start();
    }
}

我喜欢给前缀mc注意修改后的闭包。

答案 2 :(得分:1)

您的委托需要创建效果值的本地副本,以便实际评估时的值不会因为循环迭代器在线程实际评估效果之前对所有更改进行排队而发生更改。

foreach(var effect in effects)
{
    var localEffect = effect;
    var task = new Task(()=>
        {
            var result = CreatePreview(fileName, localEffect);
            Dispatcher.BeginInvoke(()=> ShowPreview(result, localEffect.DisplayName));
        });
    task.Start();
}

这将强制各个线程正确关闭效果的创建时间值。这是由于匿名委托在后台创建隐藏类的方式。

请参阅这篇文章,了解为什么你创建的内容并没有完全创建一个词法闭包,但是通过将效果复制到localEffect它会...... Anonymous method article.

答案 3 :(得分:0)

多线程是一个非常复杂的问题,存在很多问题。

绝对阅读任务并行库(或书籍)上的一两篇文章。

使用TPL的可能更正确的版本将类似于:

Parallel.ForEach(effects, currentEffect =>
{
    string result = CreatePreview(fileName, currentEffect );
    ShowPreview(result, effect.DisplayName);
}

PS。请记住,在这种情况下,最佳做法是实际并行化每个过滤操作(甚至更好地将其卸载到GPU)。