我有一个运行时间太长的应用程序,我想引入线程/并行化/无论如何。
具体来说,代码会检索几千封邮件,然后发送它们。今天,代码看起来像这样(有点简化):
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 ...
你有一个我可以尝试的简单解决方案吗?
由于
答案 0 :(得分:4)
你试过这个吗?
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
编辑:由于您仅限于.Net 3.5,我建议您使用他博客Rob Volk in this post使用的方法。两年前我用它没有问题。它在C#中,因此您需要翻译它(不超过10行代码)。
答案 1 :(得分:4)
如果你的循环体是线程安全的,你可以使用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);
}
}
答案 2 :(得分:1)
正如您所提到的,GetMailContent和Send都需要时间,并且您只能使用.NET 3.5,您可以尝试实现自己的Producer-Consumer并发模式。
基于拉动的方法
GetMailContent在一个单独的线程中工作,一旦检索到1个邮件内容,它就会将该对象放入您的自定义生成器队列中。发送工作,在其自己的线程中,并不断查询生成器队列以获取新项目。一旦可用,它就会将它出列并发送出去。
基于推送的方法
GetMailContent在单独的线程中工作并构造对象。一旦完成,它将通知Send方法,该方法在另一个线程中工作,以发送新项目。这是一种传统的观察者模式。
所有这些都需要良好的同步。您应该能够找到/实现非阻塞同步,这通常比其他阻塞同步更快。
答案 3 :(得分:0)
要使用线程,您需要确保将进程的哪个部分放入线程中。正如您所建议的那样,要将SendMail(m)
置于一个线程中,您需要确保这将有效地提高性能。如果这是大部分时间占用的唯一部分,则可以将此方法放在一个线程中。或者简单地将循环作为parelle循环。见http://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel.foreach.aspx