如何在 .net 中并排启动多个操作?

Posted

技术标签:

【中文标题】如何在 .net 中并排启动多个操作?【英文标题】:How to launch several operations side-by-side in .net? 【发布时间】:2012-01-31 19:11:30 【问题描述】:

我有一个运行时间太长的应用程序,我想引入线程/并行化/其他。

具体来说,代码会检索数千封邮件,然后发送它们。今天,代码看起来像这样(有点简化):

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...

你有一个简单的解决方案我可以试试吗?

谢谢

【问题讨论】:

【参考方案1】:

如果你的循环体是线程安全的,你可以使用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);
    

【讨论】:

看起来很有趣,但仅从 .net 4 开始可用,我们仍在 .net 3.5... 更新:为 .NET 3.5 添加解决方案【参考方案2】:

你试过了吗?

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 行代码)。

【讨论】:

AsParallel 只会并行查询枚举,实际工作会按顺序执行。考虑使用 Parallel 类来并行化工作负载。 即使提到了@oleksii 的限制,它也只能在 .net 4 中使用,我们仅限于 .net 3.5...【参考方案3】:

正如您提到的 GetMailContent 和 Send 都需要时间并且您仅限于 .NET 3.5,您可以尝试实现自己的生产者-消费者并发模式。

基于拉的方法

GetMailContent 在一个单独的线程中工作,一旦检索到 1 个邮件内容,它就会将该对象放入您的自定义生产者队列中。发送工作,在它自己的线程中,并不断向生产者队列查询新项目。一旦可用,它就会将其出列并发送出去。

基于推送的方法

GetMailContent 在单独的线程中工作并构造对象。一旦完成,它会通知在另一个线程中工作的 Send 方法有一个要发送的新项目。这是一种传统的观察者模式。

所有这些都需要良好的同步。您应该能够找到/实现非阻塞同步,这通常比替代阻塞同步更快。

【讨论】:

谢谢,但没有其他更简单的方法吗?我会修改我的问题。【参考方案4】:

要使用线程,您需要确定将进程的哪一部分放入线程中。正如您所建议的,要将SendMail(m) 放入一个线程中,您需要确保这将有效地提高性能。如果这是占用大部分时间的唯一部分,您可以将此方法放在线程中。或者简单地将循环作为一个平行循环。见http://msdn.microsoft.com/en-us/library/system.threading.tasks.parallel.foreach.aspx

【讨论】:

不,实际上是GetMailContent需要一些时间,所以我想把这两个操作(getcontent和send)放在一个线程中。 从您的代码中,我没有看到 GetMailContent 的任何用途。你确定,你正在使用它,可能这里显示的不一样吗? 是的,GetMailContent 调用一个页面需要几秒钟才能加载;它是用来检索邮件正文的,所以我确定我会调用它。我编辑了我的问题以添加 MailSenderThreaded 类。

以上是关于如何在 .net 中并排启动多个操作?的主要内容,如果未能解决你的问题,请参考以下文章

如何让div并排

如何将两个或多个元素并排放置并溢出?

如何修复错误:应用程序无法启动,因为它的并排配置不正确

如何使用 ggplot 创建并排条形图(用于多个系列)?

Latex如何插入多个图片,实现并排排列或者多行多列排列

Latex如何插入多个图片,实现并排排列或者多行多列排列