【问题标题】:Threading: incorrect Variable passing C# [closed]线程:不正确的变量传递 C# [关闭]
【发布时间】:2012-05-21 23:18:27
【问题描述】:

用户。我遇到了一个我找不到答案的问题。我对线程(在 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();
    }
}

【问题讨论】:

  • 您可能还需要提供CreatePreviewShowPreview 的代码,以便我们确定它们是否是适当的线程安全的。例如,如果您在CreatePreview 中使用全局变量作为“当前效果”,则所有三个都将引用相同的效果,即最后一个任务设置的效果。
  • 搜索“访问修改后的闭包”或“捕获的变量”,你会看到你的问题。例如:stackoverflow.com/questions/235455/access-to-modified-closure
  • 谢谢^^。这似乎奏效了。
  • 关闭此作为您的示例不再说明您的问题。将来,请在问题中保留“错误”代码,以便为修复它的答案提供上下文:)

标签: c# multithreading variables


【解决方案1】:

我现在无法测试,但我很确定你的问题是你是 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,它会在每次迭代时自动关闭变量的新副本。)

【讨论】:

  • 谢谢!这行得通。我现在要深入研究整个关闭的事情。
【解决方案2】:

您的委托需要创建 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();
}

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

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

【讨论】:

    【解决方案3】:

    您必须将当前的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 表示修改后的闭包。

    【讨论】:

      【解决方案4】:

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

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

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

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

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

      【讨论】:

      • 这并没有解决根本原因,即关闭循环变量。
      • 这会解决他创建太多线程的事实吗?代码很幼稚的事实可能在很多方面都是错误的?
      • 顺便说一句,是的,它完全解决了原来的问题,仍然感谢您的反对。
      • @Odrade:嗯,是的,实际上它确实解决了关闭问题。
      • 这样做的缺点是在 UI 线程上没有调用 ShowPreview
      猜你喜欢
      • 1970-01-01
      • 2021-02-05
      • 1970-01-01
      • 2021-06-08
      • 2012-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多