6

多个后台工作人员在 5 秒运行的进程上执行比任务更好的任何变化吗?我记得在一本书中读到任务是为短期运行的进程设计的。

我问的原因是这样的:

我有一个流程需要 5 秒才能完成,有 4000 个流程要完成。起初我是这样做的:

for (int i=0; i<4000; i++) {
    Task.Factory.StartNewTask(action);
}

这性能很差(第一分钟后,完成了 3-4 个任务,控制台应用程序有 35 个线程)。也许这很愚蠢,但我认为线程池会处理这种情况(它将所有动作放在一个队列中,当一个线程空闲时,它会采取一个动作并执行它)。

现在的第二步是手动执行 Environment.ProcessorCount 后台工作人员,并将所有操作放在 ConcurentQueue 中。所以代码看起来像这样:

var workers = new List<BackgroundWorker>();
//initialize workers

workers.ForEach((bk) =>
{
    bk.DoWork += (s, e) =>
    {
        while (toDoActions.Count > 0)
        {
            Action a;
            if (toDoActions.TryDequeue(out a))
            {
                a();
            }
        } 
    }

    bk.RunWorkerAsync();
});

这表现得更好。即使我有 30 个后台工作人员(与第一种情况一样多的任务),它的性能也比任务好得多。

乐:

我像这样开始任务:

    public static Task IndexFile(string file)
    {
        Action<object> indexAction = new Action<object>((f) =>
        {
            Index((string)f);
        });

        return Task.Factory.StartNew(indexAction, file);
    }

Index 方法是这样的:

    private static void Index(string file)
    {
        AudioDetectionServiceReference.AudioDetectionServiceClient client = new AudioDetectionServiceReference.AudioDetectionServiceClient();

        client.IndexCompleted += (s, e) =>
            {
                if (e.Error != null)
                {
                    if (FileError != null)
                    {
                        FileError(client, 
                            new FileIndexErrorEventArgs((string)e.UserState, e.Error));
                    }
                }
                else
                {
                    if (FileIndexed != null)
                    {
                        FileIndexed(client, new FileIndexedEventArgs((string)e.UserState));
                    }
                }
            };

        using (IAudio proxy = new BassProxy())
        {
            List<int> max = new List<int>();
            if (proxy.ReadFFTData(file, out max))
            {
                while (max.Count > 0 && max.First() == 0)
                {
                    max.RemoveAt(0);
                }
                while (max.Count > 0 && max.Last() == 0)
                {
                    max.RemoveAt(max.Count - 1);
                }

                client.IndexAsync(max.ToArray(), file, file);
            }
            else
            {
                throw new CouldNotIndexException(file, "The audio proxy did not return any data for this file.");
            }
        }
    }

此方法使用 Bass.net 库从 mp3 文件中读取一些数据。然后使用异步方法将该数据发送到 WCF 服务。创建任务的 IndexFile(string file) 方法在 for 循环中被调用了 4000 次。这两个事件 FileIndexed 和 FileError 没有被处理,所以它们永远不会被抛出。

4

3 回答 3

1

鉴于您有严格定义的待办事项列表,我会使用Parallel该类(For或者ForEach取决于更适合您的内容)。此外,您可以将配置参数传递给这些方法中的任何一个,以控制实际同时执行的任务数量:

        System.Threading.Tasks.Parallel.For(0, 20000, new ParallelOptions() { MaxDegreeOfParallelism = 5 }, i =>
        {
            //do something
        });

上面的代码将执行 20000 次操作,但不会同时执行超过 5 次操作。

我怀疑后台工作人员对您做得更好的原因是因为您在开始时创建并实例化了它们,而在您的示例Task代码中,您似乎正在Task为每个操作创建一个新对象。

或者,您是否考虑过在开始时使用固定数量的Task实例化对象,然后ConcurrentQueue像使用后台工作人员一样执行类似的操作?这也应该被证明是非常有效的。

于 2014-02-13T13:15:39.473 回答
1

Tasks 的性能如此差的原因是因为你挂载了太多的小任务(4000)。请记住,CPU 也需要调度任务,因此挂载大量短期任务会给 CPU 带来额外的工作负载。更多信息可以在TPL的第二段中找到:

从 .NET Framework 4 开始,TPL 是编写多线程和并行代码的首选方式。然而,并不是所有的代码都适合并行化;例如,如果循环在每次迭代中只执行少量工作,或者它没有运行多次迭代,那么并行化的开销会导致代码运行更慢。

当您使用后台工作者时,您将可能的活动线程数限制为ProcessCount。这减少了很多调度开销。

于 2014-02-13T13:27:12.910 回答
0

您是否考虑过使用线程池?

http://msdn.microsoft.com/en-us/library/system.threading.threadpool.aspx

如果您在使用线程时性能较慢,那只能是由于线程开销(分配和销毁单个线程)造成的。

于 2012-05-28T14:13:18.020 回答