Многопоточное повышение производительности - PullRequest
1 голос
/ 16 сентября 2011

Может кто-нибудь сказать мне, почему один из этих методов DoCalculation намного быстрее другого (например, на 40% быстрее)?

У меня есть основной поток, который ожидает установки ManualResetEvents:

private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
ThreadPool.QueueUserWorkItem((obj) =>
{
    ManualResetEvent[] finishcalc = new ManualResetEvent[] 
    { 
        new ManualResetEvent(false), 
        new ManualResetEvent(false), 
        new ManualResetEvent(false), 
        new ManualResetEvent(false), 
        new ManualResetEvent(false), 
        new ManualResetEvent(false) 
    };
    TimeSpan time1 = new TimeSpan(DateTime.Now.Ticks);
    DoCalculation(rand.Next(10), rand.Next(10), 1, finishcalc[0]);
    DoCalculation(rand.Next(10), rand.Next(10), 2, finishcalc[1]);
    DoCalculation(rand.Next(10), rand.Next(10), 3, finishcalc[2]);
    DoCalculation(rand.Next(10), rand.Next(10), 4, finishcalc[3]);
    DoCalculation(rand.Next(10), rand.Next(10), 5, finishcalc[4]);
    DoCalculation(rand.Next(10), rand.Next(10), 6, finishcalc[5]);

    if (WaitHandle.WaitAll(finishcalc))
    {            
        TimeSpan time2 =new TimeSpan(DateTime.Now.Ticks);
        AddTextAsync(string.Format("DoCalculation Finish in {0}\n" ,(time2-time1).TotalSeconds));
    }
});
}

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

В первом примере я создаю новый поток, и он ожидает завершения каждого последовательного вычисления, прежде чем продолжить:

void DoCalculation(int number1, int number2, int callid, ManualResetEvent calcdone)
{
    ThreadPool.QueueUserWorkItem((obj0) =>
    {
        AddTextAsync(string.Format("The values for Callid {0} are {1} and {2}\n", callid, number1, number2));
        int result = 0;
        ManualResetEvent mresetevent = new ManualResetEvent(false);
        ThreadPool.QueueUserWorkItem((obj) =>
        {
            result = number1 + number2;
            mresetevent.Set();
        });
        mresetevent.WaitOne();
        mresetevent.Reset();
        ThreadPool.QueueUserWorkItem((obj2) =>
        {
            result *= result;
            mresetevent.Set();
        });
        mresetevent.WaitOne();
        mresetevent.Reset();

        ThreadPool.QueueUserWorkItem((obj2) =>
        {
            result *= 2;
            mresetevent.Set();
        });
        mresetevent.WaitOne();
        AddTextAsync(string.Format("The result for Callid {0} is {1} \n", callid, result));
        calcdone.Set();
    });
}

Во втором примере DoCalculation я использую класс в качестве ссылкипередать Action в качестве параметра в ThreadPool и использовать его в качестве обратного вызова для создания второго и третьего потока в цепочке:

Класс связи:

public class CalcParams
{
    public int CallID;
    public ManualResetEvent ManualReset;
    public int Result;
    public Action<int, ManualResetEvent, int> CallbackDone;
}

пример службы Async::

public static void DownloadDataInBackground(CalcParams calcparams)
{
    WebClient client = new WebClient();
    Uri uri = new Uri("http://www.google.com");
    client.DownloadStringCompleted += (s, e) =>
    {
        CalcParams localparams = (CalcParams)e.UserState;
        localparams.CallbackDone(e.Result.Length + localparams.Result, localparams.ManualReset, localparams.CallID);
    };
    client.DownloadStringAsync(uri, calcparams);
}

И улучшенный метод doCalculation:

void DoCalculation(int number1, int number2, int callid, ManualResetEvent calcdone)
{
    ThreadPool.QueueUserWorkItem((obj0) =>
    {
        int result = number1+number2;
        doCalculationService.DownloadDataInBackground(new CalcParams()
        {
            Result = result,
            ManualReset = calcdone,
            CallID = callid,
            CallbackDone = (r, m, i) =>
            {
                int sqrt = r * r;
                doCalculationService.DownloadDataInBackground(new CalcParams()
                {
                    Result = sqrt,
                    CallID = i,
                    ManualReset = m,
                    CallbackDone = (r2, m2, i2) =>
                    {
                        int result2 = r2 * 2;
                        AddTextAsync(string.Format("The result for Callid {0} is {1} \n", i2, result2));
                        m2.Set();
                    }
                });
            }
        });
    });
}

Спасибо.

Ответы [ 2 ]

3 голосов
/ 16 сентября 2011

Нет веских причин для звонка ThreadPool.QueueUserWorkItem, а затем сразу же дождаться его завершения.То есть писать это:

ThreadPool.QueueUserWorkItem(() =>
    {
        // do stuff
        mevent.Set();
    });
mevent.WaitOne();

Не дает вам никакой пользы.Ваш главный поток заканчивается ожидания.На самом деле, это хуже, чем просто написать:

// do stuff

Поскольку пул потоков должен ускорять поток.

Вы можете упростить и ускорить свой первый метод DoCalculation, удалив всевложенная «асинхронная» работа:

void DoCalculation(int number1, int number2, int callid, ManualResetEvent calcdone)
{
    ThreadPool.QueueUserWorkItem((obj0) =>
    {
        AddTextAsync(string.Format("The values for Callid {0} are {1} and {2}\n", callid, number1, number2));
        int result = 0;

        result = number1 + number2;
        result *= result;
        result *= 2;

        AddTextAsync(string.Format("The result for Callid {0} is {1} \n", callid, result));
        calcdone.Set();
    });
}

Редактировать в ответ на обновленный вопрос

Ваш новый пример № 3 в некоторой степени упрощает вещи, но все же упускает из виду,Вот что происходит, когда ваш новый метод DoCalculation выполняется:

  1. ThreadQueue.QueueUserWorkItem создает новый поток и метод DoCalculation завершается.Теперь у вас есть фоновый поток.Мы назовем эту тему 1.
  2. Код вызывает DownloadDataInBackground.Этот метод запускает другой поток для асинхронной загрузки данных.Вызовите этот поток 2.
  3. Выход из потока 1.
  4. Когда поток 2 завершает загрузку, он вызывает обратный вызов завершения, который снова вызывает DownloadDataInBackground.Это создает поток 3, начинает выполнение, и поток 2 завершается.
  5. Когда поток 3 завершает загрузку, он вызывает обратный вызов завершения, выполняет вычисления, выводит некоторые данные и завершает работу.

Итак, вы запустили три темы.Никогда не было никакого значимого "многопоточности".То есть ни разу не было более одного потока, выполняющего значимую работу.

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

Ваш код был бы намного чище и выполнялся бы несколько быстрее (из-за того, что вам не нужно было запускать так много потоков), если бы вы просто написали:

ThreadPool.QueueUserWorkItem((obj0) =>
{
    DownloadString(...); // NOT DownloadStringAsync
    DownloadString(...);
    // Do calculation
});

Один поток выполняет каждую задачу последовательно.

Единственное время, когда вам нужно несколько потоков, это если вы хотите, чтобы несколько задач выполнялись одновременно.Очевидно, это не то, что вы делаете.На самом деле, ваш вопрос говорит:

Затем у меня есть метод, который создает другой поток для последовательного выполнения некоторых вычислений, то есть мне нужен результат из предыдущего потока, чтобы продолжить следующий.

Последовательные задачи означают один поток.

2 голосов
/ 20 сентября 2011

Могу ли я предложить вам рассмотреть Reactive Extensions (Rx) как альтернативный способ использования многопоточности в Silverlight?

Вот ваш код, выполненный в Rx:

Func<int, int, int> calculation = (n1, n2) =>
{
    var r = n1 + n2;
    r *= r;
    r *= 2;
    return r;
};

var query =
    from callid in Observable.Range(0, 6, Scheduler.ThreadPool)
    let n1 = rand.Next(10)
    let n2 = rand.Next(10)
    from result in Observable.Start(() => calculation(n1, n2))
    select new { callid, n1, n2, result };

query.Subscribe(x => { /* do something with result */ });

Он автоматически переносит вычисления в пул потоков - я помещаю параметр Scheduler.ThreadPool, но это значение по умолчанию для запроса SelectMany.

С таким кодом вы обычно не беспокоитесьвсе MRE и очень легко читаемый код, который может быть более легко протестирован.

Rx является поддерживаемым продуктом Microsoft и работает как на CLR для настольных ПК, так и на Silverlight.

ссылки для Rx:

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


EDIT : в соответствии с запросом в комментариях приведен пример объединения результатов каждого промежуточного вычисления с использованием Rx.

Func<int, int, int> fn1 = (n1, n2) => n1 + n2;
Func<int, int> fn2 = n => n * n;
Func<int, int> fn3 = n => 2 * n;

var query =
    from callid in Observable.Range(0, 6, Scheduler.ThreadPool)
    let n1 = rand.Next(10)
    let n2 = rand.Next(10)
    from r1 in Observable.Start(() => fn1(n1, n2))
    from r2 in Observable.Start(() => fn2(r1))
    from r3 in Observable.Start(() => fn3(r2))
    select new { callid, n1, n2, r1, r2, r3 };

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

Еще одна альтернатива, если у вас есть функции, использующие асинхронный шаблон BeginInvoke / EndInvoke, - использовать метод расширения FromAsyncPattern, подобный следующему:

Func<int, int, IObservable<int>> ofn1 =
    Observable.FromAsyncPattern<int, int, int>
        (fn1.BeginInvoke, fn1.EndInvoke);

Func<int, IObservable<int>> ofn2 =
    Observable.FromAsyncPattern<int, int>
        (fn2.BeginInvoke, fn2.EndInvoke);

Func<int, IObservable<int>> ofn3 =
    Observable.FromAsyncPattern<int, int>
        (fn3.BeginInvoke, fn3.EndInvoke);

var query =
    from callid in Observable.Range(0, 6, Scheduler.ThreadPool)
    let n1 = rand.Next(10)
    let n2 = rand.Next(10)
    from r1 in ofn1(n1, n2)
    from r2 in ofn2(r1)
    from r3 in ofn3(r2)
    select new { callid, n1, n2, r1, r2, r3 };

Немного беспорядочно, но запрос немного проще.

Примечание: параметр Scheduler.ThreadPool снова не нужен, но он просто включен, чтобы явно показать, что запрос выполняется с использованием потока.-Бассейн.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...