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

C# 线程:传递C的变量不正确#,c#,multithreading,variables,C#,Multithreading,Variables,用户。我遇到了一个我找不到答案的问题。我不太熟悉线程(在C#中),遇到了这个问题。我有一个带有特效的图像编辑器,但因为它运行得太慢,所以我尝试将它分成多个线程。问题是他总是使用效果列表中的最后一项运行“CreatePreview”命令。所以,如果我激活效果:“黑/白”、“饱和”和“绿色过滤器”,它将尝试创建3个带有绿色过滤器的预览 有人能帮我解决这个问题吗 private void CreatePreviews(string fileName, List<IEffect> effec

用户。我遇到了一个我找不到答案的问题。我不太熟悉线程(在C#中),遇到了这个问题。我有一个带有特效的图像编辑器,但因为它运行得太慢,所以我尝试将它分成多个线程。问题是他总是使用效果列表中的最后一项运行“CreatePreview”命令。所以,如果我激活效果:“黑/白”、“饱和”和“绿色过滤器”,它将尝试创建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();
    }
}
private void CreatePreviews(字符串文件名,列表效果)
{
列表任务=新列表();
foreach(var效应中的效应)
{
//添加(效果,创建预览(文件名,效果));
任务=新任务(委托()
{
字符串结果=CreatePreview(文件名、效果);
Dispatcher.BeginInvoke(新操作(
代表()
{
ShowPreview(结果、效果、显示名称);
}));
});
task.Start();
}
}

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

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();
    }
}
private void CreatePreviews(字符串文件名,列表效果)
{
列表任务=新列表();
foreach(var效应中的效应)
{
var mcEffect=效应;
任务=新任务(委托()
{
字符串结果=CreatePreview(文件名,mcEffect);
Dispatcher.BeginInvoke(新操作(
代表()
{
ShowPreview(结果、效果、显示名称);
}));
});
task.Start();
}
}

我喜欢用前缀
mc
来表示修改后的闭包。

我现在无法测试,但我很确定您的问题是您的错误

获取循环变量的副本并关闭该变量:

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,它会在每次迭代时自动关闭变量的新副本。)

多线程是一个相当复杂的问题,有很多潜在的问题

一定要读一两篇关于任务并行库的文章(或一本书)

使用TPL的可能更正确的版本如下所示:

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

请记住,这种情况下的最佳实践是实际并行每个过滤操作(或者更好地将其卸载到GPU).

您的代理需要创建effect值的本地副本,以便在线程实际计算effect之前,不会因为循环迭代器将所有更改排队而使实际计算的值发生更改

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();
}
这将强制各个线程在effect的创建时间值时正确关闭。这是由于匿名委托在后台创建隐藏类的方式造成的


请参阅这篇文章,了解为什么您所创建的并没有创建一个词法闭包,但通过将effect复制到localEffect,它将

您可能还需要提供
CreatePreview
ShowPreview
的代码,以便我们确定它们是否是线程安全的。例如,如果在“当前效果”中使用
CreatePreview
中的全局变量,这三个变量都将引用相同的效果,这是上一个任务设置的效果。搜索“访问修改的闭包”或“捕获的变量”,您将看到您的问题,例如:重复的Thank you^^。这似乎是可行的。结束这个例子不再说明你的问题。将来,在问题中保留“错误”代码,这样它就为解决问题的答案提供了上下文:)这并没有解决根本原因,这是在循环变量上关闭的。这会解决他创建太多线程的问题吗?代码幼稚的事实可能在许多方面都是错误的?顺便说一句,是的,它完全解决了最初的问题,仍然感谢否决票。@Odrade:嗯,是的,实际上它解决了闭包问题。缺点是,
ShowPreview
没有在UI线程上被调用。谢谢!这起作用了。我现在要再深入了解一下整个结束的事情。