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

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

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

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

スレッドセーフ

マルチスレッド環境において、複数のスレッド上で常に正常に実行する事が可能なコードを、スレッドセーフなコードと呼びます。

非同期処理

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

例外処理

例外処理(Exception handling)とは、プログラム実行中に異常が発生した場合、通常フローから外れ、例外として別の処理を行うようにデザインされたプログラミング言語構造です。

Q&A

解決済

2回答

10920閲覧

非同期処理の途中で中断する方法について

ibuki

総合スコア15

C#

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

スレッドセーフ

マルチスレッド環境において、複数のスレッド上で常に正常に実行する事が可能なコードを、スレッドセーフなコードと呼びます。

非同期処理

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

例外処理

例外処理(Exception handling)とは、プログラム実行中に異常が発生した場合、通常フローから外れ、例外として別の処理を行うようにデザインされたプログラミング言語構造です。

1グッド

3クリップ

投稿2018/03/07 08:42

編集2018/03/07 08:47

例えば以下のようにawait / asyncで時間のかかる処理を非同期に動かしている途中で、
キャンセルボタン等によって動作を中断して終わらせるにはどうのようにしたら良いでしょうか

C#

1private async void button_Click(object sender, EventArgs e) 2{ 3 int intValue = await GetintValue.ToString(); 4} 5private async Task<int> GetintValue() 6{ 7 await //何か重い処理 8 return 1; 9}

重い処理は内部が隠蔽されているクラスライブラリを想定しています。

umyu👍を押しています

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

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

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

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

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

guest

回答2

0

ベストアンサー

こんにちは。

async/await、Taskで「正しく」非同期キャンセルをやるなら、CancellationTokenを使います。
GetintValueメソッドに引数としてCancellationTokenを受け取るようにして、メソッド内部でIsCancellationRequestedプロパティかThrowIfCancellationRequested()メソッドを用いて途中キャンセル時のフローを構築します。
await //何か重い処理重い処理の内部で中断するには、重い処理にも再帰的にCancellationTokenを引き渡していきます。一般的な非同期ライブラリのAPIであれば、時間を要する可能性がある処理には大抵CancellationTokenを受け入れるオーバーロードが存在しています。

csharp

1private async void button_Click(object sender, EventArgs e) 2{ 3 int intValue = await GetintValue(どこかのCancellationToken).ToString(); 4} 5 6private async Task<int> GetintValue(CancellationToken token) 7{ 8 await 何か重い処理(token); // 重い処理にもtokenを渡す 9 10 if (token.IsCancellationRequested) 11 return -1; // キャンセル状態か判定して何かしたり 12 13 token.ThrowIfCancellationRequested(); // キャンセル状態だったらいきなり例外をぶっ放して止めたり 14 15 return 1; 16}

それぞれの非同期メソッドに渡すCancellationTokenは基本的にCancellationTokenSourceから生成します。そして、CancellationTokenSourceのCancel()メソッドを呼ぶことでtokenがCanceled状態になります。

csharp

1// 仮にフィールドに置く 2private readonly CancellationTokenSource cts; 3 4// ... 5{ 6 await GetintValue(this.cts.Token); // ctsからtokenを取り出して渡す 7} 8 9// ...キャンセルボタンとか 10{ 11 this.cts.Canel(); // どこかでCancel()メソッドを呼ぶと非同期に走っているGetintValueメソッドがキャンセルされる 12} 13

あと、ThrowIfCancellationRequested()メソッドを用いてキャンセルする場合、非同期メソッドのTaskの状態がRunningでもCompletedでもなくCanceled状態になるため、キャンセルされたということが明確になるためオススメです。


追記

TaskではなくIAsyncActionの非同期キャンセルということだったので追記します、おそらくTaskより簡単に処理できます。(UWP関係はほぼ触ったことないので間違ってるかもしれません)

IAsyncActionインターフェースはそれ自体がCancel()メソッドを持っており、かつAwaitableになっているものです。
awaitを使って非同期メソッドのフローに組み込む前に、それ自身のキャンセル機構を呼び出せるように設計するだけで良いです。

csharp

1private IAsyncInfo _asyncTask; // とりあえず非同期処理そのものを監視するための変数 2 3{ 4 var scan = _adapter.ScanAsync(); // まずawaitをつけないで実行し、IAsyncActionを取得 5 this._asyncTask = scan; // 先にフィールドとか外部に渡す 6 await scan; // その後await 7} 8 9// ...Cancelボタン 10{ 11 this._asyncTask?.Cancel(); // IAsyncInfoのCancel()メソッドを呼ぶだけ 12}

さらに別の方法をとして、通常のTaskのように、CancellationTokenを使ったキャンセル機構に一元化したい場合に、CancellationToken.Register()メソッドを利用することもできます。
以下はもっと良い方法を教えていただいたので末尾に追記してます。

csharp

1async Task GetintValue(CancellationToken token) 2{ 3 var scan = _adapter.ScanAsync(); // まずawaitをつけないで実行 4 token.Register(() => scan.Cancel()); // tokenがCancel状態になったときにScanAsyncをキャンセルするように登録 5 await scan; // その後await 6} 7 8// ...Cancelボタン 9{ 10 this.cts.Cancel(); // 普通のTaskのようにCancellationTokenSourceのCancel()メソッドを呼んでキャンセルできる 11}

さらに追記
IAsyncActionをTaskとして取り扱える拡張メソッドが存在することを教えていただきました。(asmさんありがとうございます)
これを利用するとCancellationTokenを利用するキャンセル処理を自然に実装できます。async/awaitやTaskと組み合わせる場合では、これが一番良い方法だと思います。

csharp

1async Task GetintValue(CancellationToken token) // 引数で受け取って 2{ 3 await _adapter.ScanAsync().AsTask(token); // AsTaskメソッドにtokenを渡して、awaitするだけ 4} 5 6// ...Cancelボタン 7{ 8 this.cts.Cancel(); // 普通のTaskのようにCancellationTokenSourceのCancel()メソッドを呼んでキャンセルできる 9}

投稿2018/03/07 16:55

編集2018/03/08 06:46
tamoto

総合スコア4103

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

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

ibuki

2018/03/08 01:32

ご回答ありがとうございます。 私が使いたいと思っているのがWindows.Devices.WiFiにあるWiFiAdapterのScanAsyncなのですが、CancellationTokenに該当するものが見つけられませんでした。 キャンセルボタンを追加してCancellationTokenSourceのCanelを走らせてみましたが、処理自体が現時点でそんなに遅くないためか完了修正したのかキャンセルされたのかよくわかりませんでした。 Taskの状態というのはデバッグモードで見れるプロパティでしょうか
tamoto

2018/03/08 02:56

IAsyncActionの非同期処理でしたか。こちらはTaskとはちょっと毛色が違うものです。 回答に追記しましたのでご確認ください。 Taskの状態というのは、TaskのStatusプロパティの設定値になります。Taskの継続処理を扱う場合に見分けがつくようになります。 CancellationTokenのキャンセル処理を目視で確認してみたいのであれば、await Task.Delay(とても大きい数値, cancellationToken); を処理に挿入してみて、Cancel()メソッドを呼ぶと素早くキャンセルされることを確認できると思います。
asm

2018/03/08 02:59

await ScanAsync().AsTask(cancellationToken); これも、できるかも
tamoto

2018/03/08 05:43

AsTask拡張メソッドあるんだ〜〜〜!! どう考えてもそれを使うべきですね!
ibuki

2018/03/08 08:32 編集

お二人ともありがとうございました。 private readonly CancellationTokenSource cts; で宣言しているctsが、実行時にnullですと言われて少し戸惑いましたが、 private CancellationTokenSource cts; としたうえで cts = new CancellationTokenSource(); を挟むことによってうまくキャンセルさせられたと思います。 この運用でも大丈夫でしたでしょうか
tamoto

2018/03/08 09:34

この回答のctsは、既にフィールドにセットしてあると仮定したものなので、実運用では初期化構文を使ってもコンストラクタで宣言でも、フィールドを使わずローカル変数で取り扱っても構いません。キャンセルしたいときにちゃんと参照が辿れるようになっていれば問題ないです。
guest

0

こんにちは。

私は、terminatedフラグを用意します。初期値はfalseで中断させたい時にtrueを設定します。そして、「何か重い処理」の中で時々それをチェックし、trueだったら中断させます。

なお、terminatedフラグをtrueにした後、安易にGatintValue()の終了を待つと確実にデッドロックするので要注意です。
ケースバイケースで対処してます。なかなか一般的な解が見つかっていませんが、私はSynchronizationContextを使うことが多いです。

投稿2018/03/07 09:00

Chironian

総合スコア23272

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問