質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Q&A

解決済

2回答

4080閲覧

C# / 非同期処理の時間後キャンセル

keruuuu

総合スコア17

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

0グッド

0クリップ

投稿2021/04/14 01:05

C#の質問です。

async/awitを使用しての非同期処理時における時間後キャンセルについてです。
現状記載のコードとしていますが、実現したい動きとなりません。
(処理開始後、5秒後には処理キャンセルとして例外処理を行いたく。)

よろしくお願いします。

実現したい動きは下記です。
・2つの処理を非同期に実施
・正常終了した場合は戻り値をTrue
5秒後に終了していない場合は戻り値をFalse

C#

1 private async Task<bool> ExecParallelTask() 2 { 3 bool isFinishedCorrectly; 4 5 // Set CancelTimer = 5 sec 6 var cts = new CancellationTokenSource(); 7 cts.CancelAfter(5000); 8 9 try 10 { 11 // Execute in parallel 12 var tasks = new List<Task>(); 13 tasks.Add(Task.Run(() => HeavyTask1(), cts.Token)); 14 tasks.Add(Task.Run(() => HeavyTask2(), cts.Token)); 15 16 // Wait for all finish 17 await Task.WhenAll(tasks.ToArray()); 18 19 // Finish: Set bool = true 20 isFinishedCorrectly = true; 21 } 22 catch (TaskCanceledException) 23 { 24 // Cancel: Set bool = false 25 isFinishedCorrectly = false; 26 } 27 finally 28 { 29 cts.Dispose(); 30 } 31 32 return isFinishedCorrectly 33 } 34 35 private void HeavyTask1() 36 { 37 重い処理 38 } 39 40 private void HeavyTask2() 41 { 42 重い処理 43 }

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2021/04/14 03:15

アプリは何か(WinFroms? WPF? コンソールアプリ? その他?)と開発環境(OS, Visual Studio のバージョン、.NET Framework or Core のどちらかとそのバージョン)を書いてください。 > 実現したい動きとなりません。 「実現したい動き」とは具体的に何で、現状はどうなるのですか?
退会済みユーザー

退会済みユーザー

2021/04/14 22:59

質問者さん、無言ですが、レスが付いているのでそれに対するフィードバックを書きましょう。役に立った/立たなかったぐらいの返事はできるのでは? 役に立たなかったなら、どこがダメなのか書いてもらえると、より期待する回答が出てくるかも。とにかく無言は NG です。
keruuuu

2021/05/07 14:53

返信遅くなり申し訳ありません。 ご指摘ありがとうございます。 まだ動作等確認できていないため、確認後またUPDATEさせて頂きます。
guest

回答2

0

ベストアンサー

HeavyTask1, HeavyTask2 メソッドの引数で CancellationToken を受けて、キャンセルされたら CancellationToken.ThrowIfCancellationRequested() で OperationCanceledException をスローするようにし、ExecParallelTask メソッドでそれを catch するようにしてはいかがですか?

具体例は以下のコードを見てください。.NET Framework 4.8 の Windows Forms アプリです。

using System; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsAsyncTest { public partial class Form5 : Form { private CancellationTokenSource cts = null; public Form5() { InitializeComponent(); EableButtons(); } // RunProcTaskAsync 起動 private async void button1_Click(object sender, EventArgs e) { DisableButtons(); this.textBox1.Text = "RunProcTaskAsync 起動"; var p = new Progress<string>(ShowProgress); using (this.cts = new CancellationTokenSource()) { // 起動してから 5 秒後にキャンセル this.cts.CancelAfter(5000); CancellationToken token = this.cts.Token; try { await RunProcTaskAsync(p, token); } catch (OperationCanceledException) { // 必要なら何らかの処置 } } EableButtons(); } // キャンセル private void button2_Click(object sender, EventArgs e) { if (this.cts != null) cts.Cancel(); } // RunProcTask 起動 private async void button3_Click(object sender, EventArgs e) { DisableButtons(); this.textBox1.Text = "RunProcTask 起動"; var p = new Progress<string>(ShowProgress); using (this.cts = new CancellationTokenSource()) { // 起動してから 5 秒後にキャンセル this.cts.CancelAfter(5000); CancellationToken token = this.cts.Token; try { await Task.Run(() => RunProcTask(p, token)); } catch (OperationCanceledException) { // 必要なら何らかの処置 } } EableButtons(); } // 処理タスク // ポーリングによるキャンセルのリッスン // IProgress<T>/Progress<T> で進捗をレポート private async Task RunProcTaskAsync(IProgress<string> progress, CancellationToken token) { int i = 0; while (true) { token.ThrowIfCancellationRequested(); await Task.Delay(1000); progress.Report(i.ToString()); i++; } } private void RunProcTask(IProgress<string> progress, CancellationToken token) { int i = 0; while (true) { token.ThrowIfCancellationRequested(); Thread.Sleep(1000); progress.Report(i.ToString()); i++; } } // 進捗を TextBox に表示するコールバック // UIスレッドで呼び出される private void ShowProgress(string log) { this.textBox1.Text = "ログ出力その " + log; } // ボタンの Enable / Disable を切り替えるヘルパメソッド private void DisableButtons() { button1.Enabled = false; button2.Enabled = true; button3.Enabled = false; } private void EableButtons() { button1.Enabled = true; button2.Enabled = false; button3.Enabled = true; } } }

実行結果は以下のようになります。[RunProcTask 起動]ボタンクリックで同期版のメソッド RunProcTask を Task.Run で起動し、そのボタンのハンドラに追加した this.cts.CancelAfter(5000); により 5 秒後にキャンセルされたところです。

イメージ説明

【追記】

キャンセル処理の基本は Microsoft のドキュメント「マネージド スレッドのキャンセル」(URL 下記)に書いてありますので読んでみてください。

マネージド スレッドのキャンセル
https://docs.microsoft.com/ja-jp/dotnet/standard/threading/cancellation-in-managed-threads

それに書いてありますが、キャンセル処理を実装するための一般的なパターンは以下の通りだそうです。

  1. CancellationTokenSource クラスのインスタンスを作成する。
  2. CancellationTokenSource.Token プロパティで CancellationToken を取得し、キャンセルをリッスンするタスクに渡す。  
  3. タスクにはキャンセル通知を適切に処置するコードを実装しておく。
  4. CancellationTokenSource.Cancel メソッドを呼び出し、リッスンしているタスクにキャンセルを通知する。
  5. キャンセル通知を受けたタスクは、あらかじめ実装されているコードに従ってキャンセル処置を行う。

 

タスクがキャンセルの通知を受けとるには、(1) ポーリング、(2) コールバックの登録、(3) 待機ハンドルの待機という方法があるそうです。上のコード例は (1) を使っています。

投稿2021/04/14 03:51

編集2021/04/15 00:53
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

keruuuu

2024/11/12 14:42 編集

回答ありがとうございます。
guest

0

HeavyTask内で、CancellationTokenSourceのIsCancellationRequestedを判定して処理を打ち切るようなコードを入れないとダメです。
あと、HeaveTaskが戻り値の無い非同期処理なら、voidではなくasync Taskにしましょう。Task内で例外が発生しても上位で受け取る事が出来ない等、色々問題が発生します。

投稿2021/04/14 02:47

編集2021/04/14 05:28
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

keruuuu

2024/11/12 14:42

回答ありがとうございます。 ベストアンサーは別の方を選ばせていただきましたが、こちらの回答も非常に参考になりました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問