Как запустить несколько операций параллельно в .net? - PullRequest
5 голосов
/ 02 января 2012

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

В частности, код получает несколько тысяч писем, а затем отправляет их. Сегодня код выглядит так (немного упрощенно):

Dim mails = centreInteretService.GetEmails()
For Each m in mails
    m.Body = GetMailContent(m)
    If MailSendable(m) Then
        SendMail(m)
    End If
Next

Я хочу попробовать отправить несколько писем параллельно. Я хотел бы попробовать с 2 потоками параллельно. Более конкретно, я бы хотел поместить весь цикл в поток (getmailcontent + sendmail).

Я думал о чем-то вроде этого:

Dim mails1 As New List(Of MailSerialiserCI)
Dim mails2 As New List(Of MailSerialiserCI)
Dim nbFirstList As Integer = CInt(Math.Ceiling(nbTotal / 2))
mails1 = mails.Take(nbFirstList)
mails2 = mails.Skip(nbFirstList)

Dim smt1 As New MailSender.MailSenderThreaded()
smt1.mails = mails1
smt1.nbTotal = nbTotal
Dim threadMails1 As ThreadStart = New ThreadStart(AddressOf smt1.SendMails)
Dim th1 As Thread = New Thread(AddressOf threadMails1)
th1.Start()

Dim smt2 As New MailSender.MailSenderThreaded()
smt2.mails = mails2
smt2.nbTotal = nbTotal
Dim threadMails2 As ThreadStart = New ThreadStart(AddressOf smt2.SendMails)
Dim th2 As Thread = New Thread(AddressOf threadMails2)
th2.Start()

И MailSenderThreaded выглядит так:

Public Class MailSenderThreaded
    Public mails As List(Of MailSerialiserCI)
    Public nbTotal As Integer
    Public Sub SendMails()
        LoopMails(Me.mails, Me.nbTotal)
    End Sub
End Class

Но строки с New Thread(AdressOf x) дают мне ошибку: no applicable function x matching delegate System.Threading.ParameterizedThreadStart.

Я пытался искать здесь и там, но я могу найти только те решения, которые требуют гораздо больше знаний, чем у меня; или основы потоков; или .NET 4, но мы все еще находимся в .NET 3.5 ...

У вас есть простое решение, которое я мог бы попробовать?

Спасибо

Ответы [ 4 ]

4 голосов
/ 02 января 2012

Если тело вашего цикла поточно-ориентированное, вы можете просто использовать Parallel.ForEach

В C # это будет выглядеть так:

var mails = centreInteretService.GetEmails();

Parallel.ForEach( mails, new ParallelOptions { MaxDegreeOfParallelism = 2 }, m =>
    {
        m.Body = GetMailContent(m);
        if ( MailSendable(m) ) SendMail(m);
    }
);

РЕДАКТИРОВАТЬ: .NET 3.5!

Я думаю, что это самое простое решение в .NET 3.5:

(Извините, это на C # - я не знаю VB. Я надеюсь, что вы можете прочитать его.)

...
List<Mail> mails = centreInteretService.GetEmails();
var mailer = new Mailer( mails );
mailer.Run();
...

public class Mailer
{
    const int THREAD_COUNT = 2;
    List<Thread> _Threads = new List<Thread>();

    List<Mail> _List = null;
    int _Index = -1;

    public Mailer( List<Mail> list )
    {
        _List = list;
    }

    public void Run()
    {
        for ( int i = 0 ; i < THREAD_COUNT ; i++ )
        {
            _Threads.Add( StartThread() );
        }

        foreach ( var thread in _Threads ) thread.Join();
    }

    Thread StartThread()
    {
        var t = new Thread( ThreadMain );
        t.Start();
        return t;
    }

    void ThreadMain()
    {
        for ( ; ; )
        {
            int index = Interlocked.Increment( ref _Index );
            if ( index >= _List.Count ) return;
            ThreadWork( _List[ index ] );
        }
    }

    void ThreadWork( Mail mail )
    {
        mail.Body = GetMailContent(mail);
        if ( MailSendable(mail) ) SendMail(mail);
    }
}
4 голосов
/ 02 января 2012

Вы пробовали это?

Dim mails = centreInteretService.GetEmails()
For Each m in mails.ASParallel()
    m.Body = GetMailContent(m)
    If MailSendable(m) Then
        SendMail(m)
    End If
Next

Это будет использовать 1 поток для каждого ядра в компьютере.Если вы хотите использовать только 2, вы можете сделать:

Dim mails = centreInteretService.GetEmails()
For Each m in mails.AsParallel().WithDegreeOfParallelism(2)
    m.Body = GetMailContent(m)
    If MailSendable(m) Then
        SendMail(m)
    End If
Next

EDIT : поскольку вы ограничены .Net 3.5, я рекомендую вам метод, используемый Роб Волк в этом посте своего блога.Я использовал его два года назад без проблем.Он находится на C #, поэтому вам нужно будет перевести его (не более 10 строк кода).

1 голос
/ 02 января 2012

Как вы упомянули, что и GetMailContent, и Send занимают много времени, и что вы ограничены .NET 3.5, вы можете попробовать реализовать свой собственный шаблон параллелизма Producer-Consumer.*

GetMailContent работает в отдельном потоке, после получения 1 почтового содержимого он помещает объект в пользовательскую очередь производителя.Send работает, находится в своем собственном потоке и постоянно запрашивает очередь производителя для нового элемента.Как только он становится доступным, он удаляет его с очереди и отправляет.

Подход на основе push

GetMailContent работает в отдельном потоке и создает объект.По завершении один из них уведомляет метод Send, работающий в другом потоке, о новом элементе для отправки.Это традиционный шаблон Observer.

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

0 голосов
/ 02 января 2012

Чтобы использовать поток, вы должны быть уверены в том, какая часть процесса должна быть помещена в поток. Как вы предлагаете, чтобы поместить SendMail(m) в поток, вы должны быть уверены, что это эффективно улучшит производительность. Если это единственная часть, которая занимает большую часть времени, вы можете поместить этот метод в поток. Или просто поместите петлю как петлю parelle. Смотри http://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel.foreach.aspx

...