4

我正在编写一个使用报表查看器创建多个 PDF 文件的获胜表单。这些 PDF 文件分为 4 个主要部分,每个部分负责创建特定报告。这些进程正在创建至少 1 个文件,最多为用户数量(当前为 50 个)。

该程序已经存在,按顺序使用了 4 种方法。为了在用户数量不断增长的情况下获得额外的性能,我想将这些方法与邮件进程分开,分成 4 个单独的线程。

虽然我是使用 C# 多线程的新手,但我阅读了许多文章如何实现这一点。我唯一不确定的是我应该从哪个方向开始。当我阅读多篇博客文章时,我不确定是使用 4 个单独的线程、一个线程池还是多个后台工作人员。(或者并行编程应该是最好的方法吗?)。博客文章告诉我是否有超过 3 个线程使用线程池,但另一方面告诉我如果使用 winforms,请使用 backgroundworker。哪个选项是最好的(为什么)?

最后,我的主线程必须等待所有进程结束才能继续。

有人可以告诉我什么是解决我的问题的最佳方法。

* 编辑后的额外信息 *

我忘了告诉(在我阅读了您的评论和可能的解决方案之后)。这些方法共享一个“IEnumerable”,仅供阅读。在触发方法(不必按顺序运行)之后,这些方法会触发用于向 UI 发送状态更新的事件。我认为使用单独的线程触发事件即使不是不可能也很困难,因此应该有某种回调函数来报告运行时的状态更新。

伪代码中的一些示例。

 main()
 {
       private List<customclass> lcc = importCustomClass()

       export.CreatePDFKind1.create(lcc.First(), exportfolderpath, arg1)

       export.CreatePDFKind2.create(lcc, exportfolderpath)

       export.CreatePDFKind3.create(lcc.First(), exportfolderpath) 

       export.CreatePDFKind4.create(customclass2, exportfolderpath)
 } 

 namespace export
 {
     class CreatePDFKind1         
     {
        create(customclass cc, string folderpath)
        {
            do something;
            reportstatus(listviewItem, status, message)
        }
     }

     class CreatePDFKind2
     {
        create(IEnumerable<customclass> lcc, string folderpath)
        {
            foreach (var x in lcc)
            {
               do something;
               reportstatus(listviewItem, status, message)
            }
        }
     }

     etc.......
  }
4

3 回答 3

5

从您描述的非常基本的图片来看,我会使用Task Parallel Library (TPL)。随附 .NET Framework 4.0+。

您谈到在生成大量线程时使用线程池的“最佳”选项。尽管这是正确的 [管理资源的最有效方式],但 TPL 为您完成所有这些 - 无需您担心任何事情。TPL 还使用多个线程并等待它们的完成也轻而易举......

为了满足您的要求,我将使用TPLContinuations。延续不仅允许您创建任务流,还可以处理您的异常。这是对 TPL的一个很好的介绍。但是给你一些想法...

您可以使用启动 TPL 任务

Task task = Task.Factory.StartNew(() => 
{
    // Do some work here...
});

现在要在前面的任务完成(错误或成功)时开始第二个任务,您可以使用该ContinueWith方法

Task task1 = Task.Factory.StartNew(() => Console.WriteLine("Antecedant Task"));
Task task2 = task1.ContinueWith(antTask => Console.WriteLine("Continuation..."));

因此,一旦task1完成、失败或被取消,就会task2“启动”并开始运行。请注意,如果task1在到达第二行代码之前已经完成,task2将被安排立即执行。antTask传递给第二个 lambda的参数是对前面任务的引用。有关更详细的示例,请参阅此链接...

您还可以传递先前任务的延续结果

Task.Factory.StartNew<int>(() => 1)
    .ContinueWith(antTask => antTask.Result * 4)
    .ContinueWith(antTask => antTask.Result * 4)
    .ContinueWith(antTask =>Console.WriteLine(antTask.Result * 4)); // Prints 64.

笔记。请务必阅读提供的第一个链接中的异常处理,因为这可能会使新手误入 TPL。

最后一件要特别关注的事情是子任务。子任务是那些被创建为AttachedToParent. 在这种情况下,直到所有子任务都完成后,延续才会运行

TaskCreationOptions atp = TaskCreationOptions.AttachedToParent;
Task.Factory.StartNew(() =>
{
    Task.Factory.StartNew(() => { SomeMethod() }, atp);
    Task.Factory.StartNew(() => { SomeOtherMethod() }, atp); 
}).ContinueWith( cont => { Console.WriteLine("Finished!") });

因此,在您的情况下,您将启动四个任务,然后在主线程上等待它们完成。

我希望这有帮助。

于 2012-11-13T16:15:48.450 回答
1

BackgroundWorker如果您需要就后台进程与 UI 进行交互,使用 a会很有帮助。如果你不这样做,那我就不打扰了。您可以直接启动 4 个Task对象:

tasks.Add(Task.Factory.StartNew(()=>DoStuff()));
tasks.Add(Task.Factory.StartNew(()=>DoStuff2()));
tasks.Add(Task.Factory.StartNew(()=>DoStuff3()));

如果您确实需要与 UI 交互;可能通过更新它以反映任务何时完成,然后我建议盯着一个 BackgroundWorker,然后再次使用任务来处理每个单独的工作单元。由于使用 a 会产生一些额外的开销,BackgroundWorker如果可以避免的话,我会避免启动它们。

BackgroundWorker bgw = new BackgroundWorker();
bgw.DoWork += (_, args) =>
{
    List<Task> tasks = new List<Task>();

    tasks.Add(Task.Factory.StartNew(() => DoStuff()));
    tasks.Add(Task.Factory.StartNew(() => DoStuff2()));
    tasks.Add(Task.Factory.StartNew(() => DoStuff3()));

    Task.WaitAll(tasks.ToArray());
};
bgw.RunWorkerCompleted += (_, args) => updateUI();
bgw.RunWorkerAsync();

您当然可以只使用Task方法来完成所有这些工作,但我仍然发现 BackgroundWorkers 在更简单的情况下使用起来更简单一些。使用 .NEt 4.5,您可以Task.WhenAll在所有 4 个任务完成后在 UI 线程中运行延续,但在 4.0 中执行此操作不会那么简单。

于 2012-11-13T16:18:06.500 回答
0

没有进一步的信息是不可能的。如果它们访问相同的资源,它们使用四种不同的方法这一事实并没有太大的区别。以 PDF 文件为例。如果您无法理解我的意思,您应该为每种方法发布一些代码,我将更详细地介绍。

由于您拥有的“部分”数量是固定的,因此无论您使用单独的线程、后台工作人员还是使用线程池,都不会产生很大的不同。我不确定为什么人们推荐背景工作者。很可能是因为它是一种更简单的多线程方法并且更难搞砸。

于 2012-11-13T16:15:37.107 回答