Автоматическое выполнение цикла

Я не знаю, имеет ли название смысл, но в приложении, которое я пишу, есть множество (расширяющих) методов. Простой пример:

Объекты:

Matter (Burn, Explode, Destroy, Freeze, Heat, Cool)
Atom (Attach, Detach)
<many more>

И пользовательская коллекция, например:

ImmutableList<T>

И такие методы:

public static class Burner
{
    public static Matter Burn ( this Matter matter )
    {
        // matter is burning ...
    }
}

var matters = new ImmutableList<Matter>();
matters.Burn();

Как видите, Burn работает с одной сущностью, но по-прежнему отображается в ImmutableList. Таким образом, я хочу сам управлять распараллеливанием (параллельным прожигом).

Как мне сделать это наиболее производительным, или самым чистым, или наиболее удобным для обслуживания способом, или комбинированным способом?

Во-первых, я бы предпочел не определять еще один метод расширения, который принимает ImmutableList внутри каждого класса (Burner и т. д.), потому что таких сотни и сотни, и они, вероятно, будут выглядеть одинаково. Но я открыт для идей.

Также весь код мой, поэтому я могу изменить/добавить что угодно в любой части кода, а не только в методах расширения.


person Joan Venge    schedule 19.08.2009    source источник


Ответы (4)


Вы можете найти это статья будет интересна для чтения. В нем обсуждается, как может работать параллельный foreach, как при самостоятельном выполнении, так и при использовании " rel="nofollow noreferrer">Параллельные расширения CTP для .NET 3.5. С помощью CTP вы можете сделать это (пример взят из статьи выше):

using System.Threading;

// A simple string collection
string[] numbers = { "One", "Two", "Three", "Four", "Five", "Six", "Seven",
  "Eight", "Nine", "Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen"};

// equivalent to: foreach (string n in numbers)
Parallel.ForEach<string>(numbers, delegate(string n)
{
  Console.WriteLine("n={0}", n.ToString());
});

Вы должны воздержаться от использования CTP в рабочем коде, если это не только для ваших собственных проектов (в этом случае вам, вероятно, стоит попробовать CTP).

person Brian    schedule 19.08.2009

Что случилось с

matters.ForEach(Burner.Burn);

с вашей собственной реализацией ForEach?

person dtb    schedule 19.08.2009
comment
Спасибо, но ForEach не параллелен, и я хочу управлять своей собственной параллелизацией в соответствии с конкретными потребностями приложения. Также я бы предпочел синтаксис в моем посте, если это возможно. - person Joan Venge; 19.08.2009
comment
Может я не правильно понимаю вашу проблему, но что вам мешает реализовать свой метод ForEach? - person dtb; 19.08.2009
comment
Я мог бы, но я хотел бы иметь такой синтаксис: Matters.Burn(); В противном случае в коде будет много вызовов ForEach, не то чтобы это плохо, но настолько очевидно, что я хочу прямой вызов, а значит, и методы расширения. - person Joan Venge; 19.08.2009
comment
Итак, вы хотите динамически создавать методы расширения для ImmutableLists в зависимости от T и вызывать их статически? Как насчет использования TextTemplatingFileGenerator для этого? - person dtb; 19.08.2009
comment
Спасибо, что такое TextTemplatingFileGenerator? Это не будет динамично. Это будет похоже на то, как если бы Burn работал на Matter, то должен быть и ImmutableList‹Matter›, такого же типа, но эта коллекция. - person Joan Venge; 19.08.2009
comment
TextTemplatingFileGenerator — это настраиваемый инструмент в Visual Studio, подобный ResXFileGenerator, за исключением того, что вы пишете небольшую программу на C#, которая генерирует выходные данные вместо предоставления файла ресурсов. См. msdn.microsoft.com/en-us/library/bb126484.aspx - person dtb; 19.08.2009
comment
Итак, я создаю этот файл, поможет ли он IntelliSense или компилятору обнаружить мои методы сбора? - person Joan Venge; 19.08.2009
comment
Вы в основном автоматизируете утомительный процесс написания всех методов расширения. В результате вы создаете файл C#, который включается в ваш проект C#, поэтому вы можете вызывать методы так, как если бы вы написали их вручную. - person dtb; 19.08.2009
comment
Спасибо, это имеет смысл. Итак, когда этот файл создается? Во время компиляции? - person Joan Venge; 19.08.2009
comment
Всякий раз, когда вы меняете маленькую программу C#, создающую файл, или когда вы щелкаете правой кнопкой мыши и выбираете Run Custom Tool. - person dtb; 19.08.2009
comment
Чем это будет отличаться от наличия другого консольного приложения, которое создает файл CS, который уже находится в решении VS? Было бы то же самое? В любом случае, я уверен, вы можете запустить его при каждой компиляции решения. - person Joan Venge; 19.08.2009
comment
stackoverflow.com/questions/280748/ - person dtb; 19.08.2009

Вот простой класс, который выполняет итерацию параллельным образом.

Эмре Айдинсерен

Использование:

Parallel.ForEach(matters, Matter=> Matter.Burn());

or

matters.ParallelForEach(matter=> Matter.Burn());

/// <summary>
/// Provides concurrent processing on a sequence of elements
/// </summary>
public static class Parallel
{
    /// <summary>
    /// Number Of parallel tasks 
    /// </summary>
    public static int NumberOfParallelTasks;


    static Parallel()
    {
        NumberOfParallelTasks =  Environment.ProcessorCount < 65 ?  Environment.ProcessorCount : 64;  
    }

    /// <summary>
    /// Performs the specified action on each element of the sequence in seperate threads.
    /// </summary>
    /// <typeparam name="T">The type of the elements of source.</typeparam>
    /// <param name="source">A sequence that contains elements to perform action</param>
    /// <param name="action">The Action delegate to perform on each element of the IEnumerable.</param>
    public static void ForEach<T>( IEnumerable<T> source, Action<T> action )
    {
        if(source == null) return;

        //create a new stack for parallel task we want to run  , stack is very performant to add and read elements in sequence
        var stacks = new Stack<T>[NumberOfParallelTasks]; 

        //instantiate stacks
        for(var i = 0;i < NumberOfParallelTasks;i++)
        {
            stacks[i] = new Stack<T>();
        }

        var itemCount = 0;

        //spread items in source to all stacks while alternating between stacks
        foreach(var item in source)
        {
            stacks[itemCount++ % NumberOfParallelTasks].Push(item);
        }

        if(itemCount==0)return;

        //if we have less items than number of Parallel tasks we should only spun threads for active stacks
        var activeStackCount = itemCount < NumberOfParallelTasks ? itemCount : NumberOfParallelTasks;

        //events are used to notify thread pool completed
        var events = new ManualResetEvent[activeStackCount];

        for(var index = 0;index < activeStackCount;index++)
        {
            //assign index to a scope variable otherwise in multithreading index will not be consistant
            var listIndex = index;

            events[listIndex] = new ManualResetEvent(false); 

            ThreadPool.QueueUserWorkItem(delegate
            {
                //name the thread for debugging
                if(String.IsNullOrEmpty(Thread.CurrentThread.Name))
                {
                    Thread.CurrentThread.Name = String.Format("Parallel.ForEach Worker Thread No:{0}", listIndex);
                }

                try
                {   
                    //iterate through our stack 
                    var stack = stacks[listIndex];
                    foreach(var item in stack)
                    {
                        action(item); 
                    }   
                }
                finally
                {
                    //fire the event to signal WaitHandle that our thread is completed
                    events[listIndex].Set();
                }

            });
        }

        WaitAll(events);

    }

    private static void WaitAll(WaitHandle[] waitHandles)
    {
        if(Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
        {
            for(var index = 0;index < waitHandles.Length;index++) WaitHandle.WaitAny(waitHandles);
        }
        else
        {
            WaitHandle.WaitAll(waitHandles); 
        }
    }

    /// <summary>
    /// Performs the specified action on each element of the sequence in seperate threads.
    /// </summary>
    /// <typeparam name="T">The type of the elements of source.</typeparam>
    /// <param name="source">A sequence that contains elements to perform action</param>
    /// <param name="action">The Action delegate to perform on each element of the IEnumerable.</param>
    public static void  ParallelForEach<T>(this IEnumerable<T> source, Action<T> action)
    {
        ForEach(source, action);
    }

}
person Community    schedule 19.08.2009
comment
Разве не должен быть только один параллельный стек задач и пул потоков. Когда каждый поток запускается, он проверяет стек на наличие работы. Если стек пуст, он завершается. Таким образом, некоторые задания длинные, а некоторые короткие, вы не закончите тем, что один поток выполняет 10 быстрых заданий и выходит, а другой выполняет 10 медленных заданий сам по себе. - person jmucchiello; 11.09.2009

создайте свое собственное расширение ForEachParallel, если вы не хотите использовать PLinq или что-то в этом роде

person mcintyre321    schedule 19.08.2009