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

本文关键字:变量 不正确 传递 线程 | 更新日期: 2023-09-27 18:35:05

users.我遇到了一个找不到答案的问题。我是线程(在 C# 中)的新手,遇到了这个问题。我有这个带有效果的图像编辑器,但由于它运行得太慢,我试图将其拆分为线程。问题是他总是使用效果列表中的最后一项运行"CreatePreview"命令。因此,如果我激活效果:"黑/白","Sature"和"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();
    }
}

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

我现在

无法测试,但我很确定你的问题是你正在关闭循环变量。

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

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

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

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,它将... 匿名方法文章。

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

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以注意修改后的闭包。

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

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

使用 TPL 的潜在更正确的版本将如下所示:

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

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