4

複数の PDF ファイルを作成するためにレポート ビューアーを使用するフォームを作成しています。これらの PDF ファイルは 4 つの主要な部分に分かれており、各部分が特定のレポートの作成を担当します。これらのプロセスは、ユーザー数 (現在は 50) まで、最小で 1 つのファイルを作成しています。

プログラムは、4 つのメソッドを順番に使用して既に存在します。ユーザー数が増加している場合にパフォーマンスを向上させるために、これらのメソッドをメール プロセスから 4 つの個別のスレッドに分離したいと考えています。

私は C# を使用してマルチスレッド化するのは初めてですが、これを実現する方法について多くの記事を読みました。唯一わからないのは、どの方法から始めればよいかということです。複数のブログ投稿を読んでいるので、4 つの個別のスレッドを使用するか、スレッド プールを使用するか、複数のバックグラウンド ワーカーを使用するかがわかりません。(または、並列プログラミングが最善の方法である必要がありますか?)。ブログの投稿は、3 つ以上のスレッドがスレッド プールを使用しているかどうかを教えてくれますが、一方、winforms を使用している場合は、backgroundworker を使用しているかどうかを教えてくれます。最適なオプション (およびその理由) はどれですか?

最後に、メインスレッドはすべてのプロセスが終了するのを待ってから続行する必要があります。

誰かが私の問題に対する最善の解決策を教えてもらえますか?

*編集後の追加情報*

言い忘れたこと(あなたのコメントと考えられる解決策をすべて読んだ後)。メソッドは、読み取り専用の 1 つの「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 Parallell Library (TPL)を使用します。.NET Framework 4.0+ に同梱されています。

大量から中程度の数のスレッドを生成するときにスレッドプールを使用する「最良の」オプションについて話します。これが正しい [リソースを操作する最も効率的な方法] にもかかわらず、TPL はこれらすべてをあなたに代わって行います - あなたは何も心配する必要はありません。TPL はまた、複数のスレッドを使用し、それらの完了を待つのも面倒です...

必要なことを行うには、TPLContinuationsを使用します。継続により、タスクのフローを作成できるだけでなく、例外も処理できます。これは、TPLの優れた入門書です。しかし、あなたにいくつかのアイデアを与えるために...

を使用して TPL タスクを開始できます。

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

ContinueWith先行タスクが (エラーまたは正常に) 終了したときに 2 番目のタスクを開始するには、次のメソッドを使用できます。

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

task1そのため、完了、失敗、またはキャンセルされるとすぐにtask2「起動」して実行を開始します。task1コードの 2 行目に到達する前に完了した場合は、task2すぐに実行するようにスケジュールされることに注意してください。2 番目のantTaskラムダに渡される引数は、先行タスクへの参照です。詳細な例については、このリンクを参照してください...

継続元タスクから継続結果を渡すこともできます

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 に迷う可能性があります。

特に必要なものを確認する最後の 1 つは、子タスクです。子タスクは、 として作成されるものAttachedToParentです。この場合、すべての子タスクが完了するまで継続は実行されません

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

したがって、あなたのケースでは、4 つのタスクを開始し、メイン スレッドでそれらの完了を待ちます。

これが役立つことを願っています。

于 2012-11-13T16:15:48.450 に答える
1

BackgroundWorkerバックグラウンド プロセスに関して UI を操作する必要がある場合は、 を使用すると便利です。そうでなければ、私はそれを気にしません。Task4 つのオブジェクトを直接開始できます。

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

UI を操作する必要がある場合。おそらく、タスクがいつ終了したかを反映するように更新することによって、1つ BackgroundWorkerを見つめてから、タスクを再度使用して個々の作業単位を処理することをお勧めします。を使用すると追加のオーバーヘッドが発生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

それ以上の情報がなければ、それを伝えることは不可能です。それらが 4 つの別々のメソッドにあるという事実は、それらが同じリソースにアクセスしている場合、大きな違いはありません。例えばPDFファイル。私が言っていることの理解に問題がある場合は、各メソッドのコードの一部を投稿してください。もう少し詳しく説明します。

使用する「パーツ」の数は固定されているため、個別のスレッド、バックグラウンド ワーカー、またはスレッド プールを使用するかどうかに大きな違いはありません。バックグラウンド ワーカーを推奨する理由がよくわかりません。おそらく、マルチスレッド化へのアプローチが単純であり、台無しにするのがより難しいためです。

于 2012-11-13T16:15:37.107 に答える