Symfoware

Symfowareについての考察blog

C# BackgroundWorker 処理状況の通知とキャンセル、複数タスクの実行(.NET Core 2)

C#で非同期処理を行う場合、BackgroundWorkerを使うのが便利です。
C# 重い処理をBackGroundServiceで実行する

Ubuntuにインストールした.NET Core 2で動作を確認してみます。
Ubuntu 18.04に.NET Core 2をインストールしてC#プログラムをビルドする


進捗表示



前回のサンプルをそのまま実行してみます。


  1. using System;
  2. using System.Threading;
  3. using System.ComponentModel;
  4. namespace sample
  5. {
  6.     class Program
  7.     {
  8.         static void Main(string[] args)
  9.         {
  10.             Program p = new Program();
  11.             p.ProcStart();
  12.         }
  13.         private void ProcStart() {
  14.             
  15.             BackgroundWorker worker = new BackgroundWorker();
  16.             
  17.             //処理の途中経過報告を行う場合
  18.             worker.WorkerReportsProgress = true;
  19.             
  20.             //実行する重い処理を指定
  21.             worker.DoWork += new DoWorkEventHandler(ProcHeavy_DoWork);
  22.             //DoWorkが終了した時に呼び出されるメソッド
  23.             worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ProcEnd);
  24.             //途中経過を処理するメソッド
  25.             worker.ProgressChanged += new ProgressChangedEventHandler(ProcState);
  26.             
  27.             
  28.             Console.WriteLine("処理開始");
  29.             
  30.             //RunWorkerAsyncでDoWorkに指定したメソッドが実行される
  31.             worker.RunWorkerAsync();
  32.             
  33.             Console.WriteLine("Async終了");
  34.             
  35.             //DoWorkが終了するまで待つ
  36.             //待たないと、コンソールアプリケーションの場合、すぐに処理が終了する
  37.             while(worker.IsBusy) {
  38.                 Thread.Sleep(100);
  39.             }
  40.             Console.WriteLine("IsBusy終了");
  41.         }
  42.         
  43.         //重い処理部分
  44.         private void ProcHeavy_DoWork(object sender, DoWorkEventArgs e) {
  45.             
  46.             BackgroundWorker worker = sender as BackgroundWorker;
  47.             
  48.             for (int i = 0; i < 10; i++) {
  49.                 Console.WriteLine(i + "回目処理中...");
  50.                 Thread.Sleep(1000);
  51.                 
  52.                 //進捗報告
  53.                 worker.ReportProgress(i, "処理成功");
  54.             }
  55.             
  56.             e.Result = "重い処理終了";
  57.         }
  58.         
  59.         //重い処理が終了した時に呼び出される部分
  60.         private void ProcEnd(object sender, RunWorkerCompletedEventArgs e) {
  61.             
  62.             Console.WriteLine("処理終了");
  63.             Console.WriteLine("結果:" + e.Result);
  64.             
  65.         }
  66.         
  67.         
  68.         private void ProcState(object sender, ProgressChangedEventArgs e) {
  69.             int count = e.ProgressPercentage;
  70.             string state = e.UserState as string;
  71.             Console.WriteLine("===" + count + ":" + state + "===");
  72.         }
  73.     }
  74. }



実行結果


$ dotnet run
処理開始
Async終了
0回目処理中...
1回目処理中...
===0:処理成功===
===1:処理成功===
2回目処理中...
3回目処理中...
===2:処理成功===
===3:処理成功===
4回目処理中...
5回目処理中...
===4:処理成功===
6回目処理中...
===5:処理成功===
7回目処理中...
===6:処理成功===
8回目処理中...
===7:処理成功===
9回目処理中...
===8:処理成功===
処理終了
結果:重い処理終了
===9:処理成功===
IsBusy終了




狙い通り、スレッドで実行してくれました。




キャンセル処理



5回めの処理が終わったら、キャンセルしてみます。
こちらを参考にさせていただきました。
BackgroundWorkerの使い方 - C#

WorkerSupportsCancellation = true
を設定しておき、
CancellationPending
を確認しながら処理を実行すれば良いようです。

サンプルはこんな感じになりました。


  1. using System;
  2. using System.Threading;
  3. using System.ComponentModel;
  4. namespace sample
  5. {
  6.     class Program
  7.     {
  8.         private bool _callCancel = false;
  9.         static void Main(string[] args)
  10.         {
  11.             Program p = new Program();
  12.             p.ProcStart();
  13.         }
  14.         private void ProcStart() {
  15.             
  16.             BackgroundWorker worker = new BackgroundWorker();
  17.             
  18.             //処理の途中経過報告を行う場合
  19.             worker.WorkerReportsProgress = true;
  20.             // キャンセルを行う場合
  21.             worker.WorkerSupportsCancellation = true;
  22.             
  23.             //実行する重い処理を指定
  24.             worker.DoWork += new DoWorkEventHandler(ProcHeavy_DoWork);
  25.             //DoWorkが終了した時に呼び出されるメソッド
  26.             worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ProcEnd);
  27.             //途中経過を処理するメソッド
  28.             worker.ProgressChanged += new ProgressChangedEventHandler(ProcState);
  29.             
  30.             
  31.             Console.WriteLine("処理開始");
  32.             
  33.             //RunWorkerAsyncでDoWorkに指定したメソッドが実行される
  34.             worker.RunWorkerAsync();
  35.             
  36.             Console.WriteLine("Async終了");
  37.             
  38.             //DoWorkが終了するまで待つ
  39.             //待たないと、コンソールアプリケーションの場合、すぐに処理が終了する
  40.             while(worker.IsBusy) {
  41.                 if (_callCancel) {
  42.                     worker.CancelAsync();
  43.                     _callCancel = false;
  44.                 }
  45.                 Thread.Sleep(100);
  46.             }
  47.             Console.WriteLine("IsBusy終了");
  48.             
  49.         }
  50.         
  51.         //重い処理部分
  52.         private void ProcHeavy_DoWork(object sender, DoWorkEventArgs e) {
  53.             
  54.             BackgroundWorker worker = sender as BackgroundWorker;
  55.             
  56.             for (int i = 0; i < 10; i++) {
  57.                 Console.WriteLine(i + "回目処理中...");
  58.                 Thread.Sleep(1000);
  59.                 
  60.                 //進捗報告
  61.                 worker.ReportProgress(i, "処理成功");
  62.                 // キャンセルされたかチェック
  63.                 if (worker.CancellationPending) {
  64.                     e.Cancel = true;
  65.                     break;
  66.                 }
  67.             }
  68.             
  69.             e.Result = "重い処理終了";
  70.         }
  71.         
  72.         //重い処理が終了した時に呼び出される部分
  73.         private void ProcEnd(object sender, RunWorkerCompletedEventArgs e) {
  74.             
  75.             if (e.Cancelled) {
  76.                 Console.WriteLine("途中でキャンセルされました");
  77.                 return;
  78.             }
  79.             Console.WriteLine("結果:" + e.Result);
  80.             Console.WriteLine("処理終了");
  81.             
  82.         }
  83.         
  84.         
  85.         private void ProcState(object sender, ProgressChangedEventArgs e) {
  86.             int count = e.ProgressPercentage;
  87.             string state = e.UserState as string;
  88.             Console.WriteLine("===" + count + ":" + state + "===");
  89.             // 5回目の処理完了でキャンセル実行
  90.             if (count == 5) {
  91.                 _callCancel = true;
  92.             }
  93.         }
  94.     }
  95. }




実行結果


$ dotnet run
処理開始
Async終了
0回目処理中...
1回目処理中...
===0:処理成功===
===1:処理成功===
2回目処理中...
3回目処理中...
===2:処理成功===
4回目処理中...
===3:処理成功===
5回目処理中...
===4:処理成功===
6回目処理中...
===5:処理成功===
途中でキャンセルされました
===6:処理成功===
IsBusy終了





RunWorkerAsyncに引数を渡す



複数のタスクを並行して実行できるか試してみます。
引数の渡し方はこちらが参考になりました。
C# .NET Frameworkでバックグラウンドワーカーに自由度を持たせる

DoWorkEventArgsのArgumentで取得できますね。
こんなプログラムを書いてみました。


  1. using System;
  2. using System.Threading;
  3. using System.ComponentModel;
  4. namespace sample
  5. {
  6.     class Program
  7.     {
  8.         static void Main(string[] args)
  9.         {
  10.             Program p = new Program();
  11.             p.ProcStart();
  12.         }
  13.         private void ProcStart() {
  14.             
  15.             BackgroundWorker worker = new BackgroundWorker();
  16.             
  17.             //処理の途中経過報告を行う場合
  18.             worker.WorkerReportsProgress = true;
  19.             // キャンセルを行う場合
  20.             worker.WorkerSupportsCancellation = true;
  21.             
  22.             //実行する重い処理を指定
  23.             worker.DoWork += new DoWorkEventHandler(ProcHeavy_DoWork);
  24.             //DoWorkが終了した時に呼び出されるメソッド
  25.             worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ProcEnd);
  26.             //途中経過を処理するメソッド
  27.             worker.ProgressChanged += new ProgressChangedEventHandler(ProcState);
  28.             
  29.             
  30.             Console.WriteLine("処理開始");
  31.             
  32.             //RunWorkerAsyncでDoWorkに指定したメソッドが実行される
  33.             worker.RunWorkerAsync("proc1");
  34.             worker.RunWorkerAsync("proc2");
  35.             worker.RunWorkerAsync("proc3");
  36.             
  37.             Console.WriteLine("Async終了");
  38.             
  39.             //DoWorkが終了するまで待つ
  40.             //待たないと、コンソールアプリケーションの場合、すぐに処理が終了する
  41.             while(worker.IsBusy) {
  42.                 Thread.Sleep(100);
  43.             }
  44.             Console.WriteLine("IsBusy終了");
  45.             
  46.         }
  47.         
  48.         //重い処理部分
  49.         private void ProcHeavy_DoWork(object sender, DoWorkEventArgs e) {
  50.             
  51.             BackgroundWorker worker = sender as BackgroundWorker;
  52.             string processName = e.Argument as string;
  53.             
  54.             for (int i = 0; i < 5; i++) {
  55.                 Console.WriteLine(string.Format("{0}:{1}回目処理中...", processName, i));
  56.                 Thread.Sleep(1000);
  57.                 
  58.                 //進捗報告
  59.                 worker.ReportProgress(i, string.Format("{0}:処理成功", processName));
  60.             }
  61.             
  62.             e.Result = processName;
  63.         }
  64.         
  65.         //重い処理が終了した時に呼び出される部分
  66.         private void ProcEnd(object sender, RunWorkerCompletedEventArgs e) {
  67.             
  68.             Console.WriteLine(string.Format("{0}処理終了", e.Result));
  69.             
  70.         }
  71.         
  72.         
  73.         private void ProcState(object sender, ProgressChangedEventArgs e) {
  74.             int count = e.ProgressPercentage;
  75.             string state = e.UserState as string;
  76.             Console.WriteLine(string.Format("=== {0} : {1} ===", state, count));
  77.         }
  78.     }
  79. }



実行するとエラーに。


$ dotnet run
処理開始
proc1:0回目処理中...

Unhandled Exception: System.InvalidOperationException: This BackgroundWorker is currently busy and cannot run multiple tasks concurrently.
at System.ComponentModel.BackgroundWorker.RunWorkerAsync(Object argument)
at sample.Program.ProcStart() in /home/baranche/dotnet/sample/Program.cs:line 37
at sample.Program.Main(String[] args) in /home/baranche/dotnet/sample/Program.cs:line 13



BackgroundWorkerって1つのタスクしか実行できないんですね。
なるほど。



複数タスクの実行



BackgroundWorkerで複数のタスクを実行させたい場合、
その数分BackgroundWorkerのインスタンスを作ってしまえば良いようです。


  1. using System;
  2. using System.Threading;
  3. using System.ComponentModel;
  4. namespace sample
  5. {
  6.     class Program
  7.     {
  8.         static void Main(string[] args)
  9.         {
  10.             Program p = new Program();
  11.             p.ProcStart();
  12.         }
  13.         private void ProcStart() {
  14.             
  15.             BackgroundWorker[] workers = new BackgroundWorker[3];
  16.             
  17.             for(int i = 0; i < workers.Length; i++) {
  18.                 workers[i] = new BackgroundWorker();
  19.                 //処理の途中経過報告を行う場合
  20.                 workers[i].WorkerReportsProgress = true;
  21.                 // キャンセルを行う場合
  22.                 workers[i].WorkerSupportsCancellation = true;
  23.                 
  24.                 //実行する重い処理を指定
  25.                 workers[i].DoWork += new DoWorkEventHandler(ProcHeavy_DoWork);
  26.                 //DoWorkが終了した時に呼び出されるメソッド
  27.                 workers[i].RunWorkerCompleted += new RunWorkerCompletedEventHandler(ProcEnd);
  28.                 //途中経過を処理するメソッド
  29.                 workers[i].ProgressChanged += new ProgressChangedEventHandler(ProcState);
  30.                 
  31.             }
  32.             
  33.             
  34.             Console.WriteLine("処理開始");
  35.             
  36.             //RunWorkerAsyncでDoWorkに指定したメソッドが実行される
  37.             workers[0].RunWorkerAsync("proc1");
  38.             workers[1].RunWorkerAsync("proc2");
  39.             workers[2].RunWorkerAsync("proc3");
  40.             
  41.             Console.WriteLine("Async終了");
  42.             
  43.             //DoWorkが終了するまで待つ
  44.             //待たないと、コンソールアプリケーションの場合、すぐに処理が終了する
  45.             while(workers[0].IsBusy || workers[1].IsBusy || workers[2].IsBusy) {
  46.                 Thread.Sleep(100);
  47.             }
  48.             Console.WriteLine("IsBusy終了");
  49.             
  50.         }
  51.         
  52.         //重い処理部分
  53.         private void ProcHeavy_DoWork(object sender, DoWorkEventArgs e) {
  54.             
  55.             BackgroundWorker worker = sender as BackgroundWorker;
  56.             string processName = e.Argument as string;
  57.             
  58.             for (int i = 0; i < 5; i++) {
  59.                 Console.WriteLine(string.Format("{0}:{1}回目処理中...", processName, i));
  60.                 Thread.Sleep(1000);
  61.                 
  62.                 //進捗報告
  63.                 worker.ReportProgress(i, string.Format("{0}:処理成功", processName));
  64.             }
  65.             
  66.             e.Result = processName;
  67.         }
  68.         
  69.         //重い処理が終了した時に呼び出される部分
  70.         private void ProcEnd(object sender, RunWorkerCompletedEventArgs e) {
  71.             
  72.             Console.WriteLine(string.Format("{0}処理終了", e.Result));
  73.             
  74.         }
  75.         
  76.         
  77.         private void ProcState(object sender, ProgressChangedEventArgs e) {
  78.             int count = e.ProgressPercentage;
  79.             string state = e.UserState as string;
  80.             Console.WriteLine(string.Format("=== {0} : {1} ===", state, count));
  81.         }
  82.     }
  83. }



実行結果


$ dotnet run
処理開始
Async終了
proc1:0回目処理中...
proc2:0回目処理中...
proc3:0回目処理中...
proc1:1回目処理中...
proc2:1回目処理中...
proc3:1回目処理中...
proc1:2回目処理中...
=== proc1:処理成功 : 0 ===
=== proc2:処理成功 : 0 ===
proc2:2回目処理中...
=== proc3:処理成功 : 0 ===
=== proc1:処理成功 : 1 ===
=== proc2:処理成功 : 1 ===
=== proc3:処理成功 : 1 ===
=== proc1:処理成功 : 2 ===
proc3:2回目処理中...
proc2:3回目処理中...
proc1:3回目処理中...
=== proc2:処理成功 : 2 ===
proc3:3回目処理中...
=== proc3:処理成功 : 2 ===
=== proc2:処理成功 : 3 ===
=== proc1:処理成功 : 3 ===
proc2:4回目処理中...
proc1:4回目処理中...
=== proc3:処理成功 : 3 ===
proc3:4回目処理中...
=== proc2:処理成功 : 4 ===
proc2処理終了
=== proc1:処理成功 : 4 ===
proc1処理終了
=== proc3:処理成功 : 4 ===
proc3処理終了
IsBusy終了





QueueとIsBusyによる制御



上記の例だと、実行したいタスクが増えた時大変です。
BackgroundWorkerのインスタンス数は限定し、IsBusyの場合はQueueに入れて待機してみます。


  1. using System;
  2. using System.Threading;
  3. using System.ComponentModel;
  4. using System.Collections.Generic;
  5. namespace sample
  6. {
  7.     class Program
  8.     {
  9.         static void Main(string[] args)
  10.         {
  11.             Program p = new Program();
  12.             p.ProcStart();
  13.         }
  14.         private void ProcStart() {
  15.             
  16.             BackgroundWorker[] workers = new BackgroundWorker[2];
  17.             
  18.             for(int i = 0; i < workers.Length; i++) {
  19.                 workers[i] = new BackgroundWorker();
  20.                 //処理の途中経過報告を行う場合
  21.                 workers[i].WorkerReportsProgress = true;
  22.                 // キャンセルを行う場合
  23.                 workers[i].WorkerSupportsCancellation = true;
  24.                 
  25.                 //実行する重い処理を指定
  26.                 workers[i].DoWork += new DoWorkEventHandler(ProcHeavy_DoWork);
  27.                 //DoWorkが終了した時に呼び出されるメソッド
  28.                 workers[i].RunWorkerCompleted += new RunWorkerCompletedEventHandler(ProcEnd);
  29.                 //途中経過を処理するメソッド
  30.                 workers[i].ProgressChanged += new ProgressChangedEventHandler(ProcState);
  31.                 
  32.             }
  33.             
  34.             
  35.             Console.WriteLine("処理開始");
  36.             // 実行したい処理を一旦キューに入れる
  37.             Queue<string> tasks = new Queue<string>();
  38.             tasks.Enqueue("proc1");
  39.             tasks.Enqueue("proc2");
  40.             tasks.Enqueue("proc3");
  41.             tasks.Enqueue("proc4");
  42.             
  43.             Console.WriteLine("Async終了");
  44.             
  45.             //DoWorkが終了するまで待つ
  46.             //待たないと、コンソールアプリケーションの場合、すぐに処理が終了する
  47.             while(workers[0].IsBusy || workers[1].IsBusy || (tasks.Count != 0)) {
  48.                 if (0 < tasks.Count) {
  49.                     if (!workers[0].IsBusy) {
  50.                         workers[0].RunWorkerAsync(tasks.Dequeue());
  51.                         continue;
  52.                     }
  53.                     if (!workers[1].IsBusy) {
  54.                         workers[1].RunWorkerAsync(tasks.Dequeue());
  55.                         continue;
  56.                     }
  57.                 }
  58.                 Thread.Sleep(100);
  59.             }
  60.             Console.WriteLine("IsBusy終了");
  61.             
  62.         }
  63.         
  64.         //重い処理部分
  65.         private void ProcHeavy_DoWork(object sender, DoWorkEventArgs e) {
  66.             
  67.             BackgroundWorker worker = sender as BackgroundWorker;
  68.             string processName = e.Argument as string;
  69.             
  70.             for (int i = 0; i < 5; i++) {
  71.                 Console.WriteLine(string.Format("{0}:{1}回目処理中...", processName, i));
  72.                 Thread.Sleep(1000);
  73.                 
  74.                 //進捗報告
  75.                 worker.ReportProgress(i, string.Format("{0}:処理成功", processName));
  76.             }
  77.             
  78.             e.Result = processName;
  79.         }
  80.         
  81.         //重い処理が終了した時に呼び出される部分
  82.         private void ProcEnd(object sender, RunWorkerCompletedEventArgs e) {
  83.             
  84.             Console.WriteLine(string.Format("{0}処理終了", e.Result));
  85.             
  86.         }
  87.         
  88.         
  89.         private void ProcState(object sender, ProgressChangedEventArgs e) {
  90.             int count = e.ProgressPercentage;
  91.             string state = e.UserState as string;
  92.             Console.WriteLine(string.Format("=== {0} : {1} ===", state, count));
  93.         }
  94.     }
  95. }




狙い通りの実行結果です。


$ dotnet run
処理開始
Async終了
proc1:0回目処理中...
proc2:0回目処理中...
proc1:1回目処理中...
proc2:1回目処理中...
=== proc1:処理成功 : 0 ===
=== proc2:処理成功 : 0 ===
=== proc1:処理成功 : 1 ===
proc2:2回目処理中...
proc1:2回目処理中...
=== proc2:処理成功 : 1 ===
proc2:3回目処理中...
proc1:3回目処理中...
=== proc2:処理成功 : 2 ===
=== proc1:処理成功 : 2 ===
proc1:4回目処理中...
proc2:4回目処理中...
=== proc2:処理成功 : 3 ===
=== proc1:処理成功 : 3 ===
=== proc1:処理成功 : 4 ===
=== proc2:処理成功 : 4 ===
proc2処理終了
proc1処理終了
proc3:0回目処理中...
proc4:0回目処理中...
proc4:1回目処理中...
proc3:1回目処理中...
=== proc3:処理成功 : 0 ===
=== proc4:処理成功 : 0 ===
proc4:2回目処理中...
proc3:2回目処理中...
=== proc3:処理成功 : 1 ===
=== proc4:処理成功 : 1 ===
proc3:3回目処理中...
=== proc4:処理成功 : 2 ===
=== proc3:処理成功 : 2 ===
proc4:3回目処理中...
proc3:4回目処理中...
=== proc3:処理成功 : 3 ===
proc4:4回目処理中...
=== proc4:処理成功 : 3 ===
proc3処理終了
proc4処理終了
=== proc4:処理成功 : 4 ===
=== proc3:処理成功 : 4 ===
IsBusy終了


関連記事

テーマ:プログラミング - ジャンル:コンピュータ

  1. 2018/06/10(日) 17:34:53|
  2. 備忘録
  3. | トラックバック:0
  4. | コメント:0
  5. | 編集
<<Python + OpenCV3 指定領域のノイズを修復する(Inpaint) | ホーム | wkhtmltopdfでhtmlをpdfに変換>>

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック

トラックバック URL
https://symfoware.blog.fc2.com/tb.php/2191-e0cbc337
この記事にトラックバックする(FC2ブログユーザー)