2

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

4 回答 4

5

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

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

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

于 2012-05-21T23:25:24.000 回答
1

您的委托需要创建 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 它将... 匿名方法文章。

于 2012-05-21T23:29:42.890 回答
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我喜欢给注释修改后的闭包添加前缀。

于 2012-05-21T23:24:39.360 回答
0

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

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

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

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

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

于 2012-05-21T23:28:16.577 回答