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

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

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

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

非同期処理

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

Q&A

解決済

2回答

11304閲覧

先に実行されているタスクがあればキャンセルして終了を待ってから実行する

twck

総合スコア314

C#

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

非同期処理

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

0グッド

0クリップ

投稿2018/10/16 02:41

編集2018/10/18 04:27

前提・実現したいこと

非同期で実行したいタスクがありますが、そのタスクは同時に複数動かしたくありません。
先に実行されているタスクがあった場合は、そのタスクをキャンセルして、終了を待ってから新しいタスクを実行したいです。

以下のように作ってみて一応動作しているものの、非同期処理の知識が無いためこの作り方であっているのか自信がありません。
もっと適した作り方があると思うのですが、皆さんならどう作られますか?

該当のソースコード

C#

1private int taskNo = 0; 2 3private List<CancellationTokenSource> cancelTokenSources = new List<CancellationTokenSource>(); 4 5private async void button1_Click(object sender, EventArgs e) 6{ 7 var no = this.taskNo++; 8 9 CancellationTokenSource cts = null; 10 try 11 { 12 // 自身の CancellationTokenSource を生成してリストに登録 13 lock (cancelTokenSources) 14 { 15 cts = new CancellationTokenSource(); 16 cancelTokenSources.Add(cts); 17 } 18 19 // 先に実行しているタスクが終了するまで待機するループ 20 while (true) 21 { 22 lock (cancelTokenSources) 23 { 24 // 最後に登録されたCancellationTokenSource以外はキャンセルする 25 for (var i = 0; i < (cancelTokenSources.Count - 1); i++) 26 { 27 cancelTokenSources[i].Cancel(); 28 } 29 30 // キャンセル途中のCancellationTokenSourceがなければループを抜ける 31 if (cancelTokenSources.Count(n => n.IsCancellationRequested) <= 0) { break; } 32 } 33 34 // 自身がキャンセルされた場合はループを抜ける 35 if (cts.IsCancellationRequested) { break; } 36 37 await Task.Delay(100); 38 }; 39 40 // 自身がキャンセルされていなければタスクを実行する 41 // 自身がキャンセルされている場合はタスクの生成すらしない 42 if (cts.IsCancellationRequested == false) 43 { 44 int r = await Task1(cts.Token, no); 45 } 46 } 47 finally 48 { 49 // 自身の CancellationTokenSource をリストから削除して廃棄 50 if (cts != null) 51 { 52 lock (cancelTokenSources) 53 { 54 cancelTokenSources.Remove(cts); 55 cts.Dispose(); 56 } 57 } 58 } 59} 60 61// 非同期だが同時に複数実行したくないタスク(内容に意味はない) 62private async Task<int> Task1(CancellationToken token, int no) 63{ 64 Console.WriteLine(no + ":start"); 65 for (int i = 0; i < 10; i++) 66 { 67 Console.WriteLine(no + ":" + i); 68 if (token.IsCancellationRequested) 69 { 70 Console.WriteLine(no + ":cancel"); 71 return -1; 72 } 73 await Task.Delay(1000); 74 } 75 Console.WriteLine(no + ":end"); 76 return 1; 77}

補足情報(FW/ツールのバージョンなど)

Visual Studio Community 2017
.NET Framework 4.7

追記

実行しやすいようにと、私が不用意にサンプルコードをフォームアプリケーションで書いてしまったために、ユーザーによる操作を想定していると誤解させてしまいました。
実際には button1_Clickメソッドの部分は他のプログラムから非常に素早く連続実行される可能性も想定しています。
極端に言えば以下のように並列実行されても問題なく4つがキャンセルされて、最後の1つだけ実行が続く感じです。

C#

1Parallel.Invoke( 2 () => button1_Click(null, null), 3 () => button1_Click(null, null), 4 () => button1_Click(null, null), 5 () => button1_Click(null, null), 6 () => button1_Click(null, null) 7);

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

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

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

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

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

guest

回答2

0

ベストアンサー

みなさんならどうしますかということなので、私なら別のクラスに分離します。
コメント欄にて指摘いただいたので書き直しました。

C#

1using System; 2using System.Diagnostics; 3using System.Threading; 4using System.Threading.Tasks; 5using System.Windows.Forms; 6 7namespace WindowsFormsApp1 8{ 9 public partial class Form1 : Form 10 { 11 public Form1() 12 { 13 InitializeComponent(); 14 } 15 16 private int taskId = 0; 17 18 private void button1_Click(object sender, EventArgs e) 19 { 20 for (int j = 0; j < 10; j++) 21 { 22 Something.DoSomething(async ct => 23 { 24 taskId++; 25 for (int i = 0; i < 100; i++) 26 { 27 if (ct.IsCancellationRequested) break; 28 Debug.WriteLine($"{taskId}:{i}:1"); 29 await Task.Delay(100); 30 Debug.WriteLine($"{taskId}:{i}:2"); 31 } 32 }); 33 } 34 } 35 } 36 37 static class Something 38 { 39 static CancellationTokenSource cts; 40 static SemaphoreSlim semaphore = new SemaphoreSlim(1, 1); 41 static object lockObject = new object(); 42 43 public static Task DoSomething(Func<CancellationToken, Task> action) 44 { 45 CancellationToken token; 46 lock (lockObject) 47 { 48 Cancel(); 49 cts = new CancellationTokenSource(); 50 token = cts.Token; 51 } 52 return Task.Run(async () => 53 { 54 await semaphore.WaitAsync(); 55 try 56 { 57 await action(token); 58 } 59 finally 60 { 61 semaphore.Release(); 62 } 63 }); 64 } 65 66 public static void Cancel() 67 { 68 cts?.Cancel(); 69 } 70 } 71}

投稿2018/10/16 06:36

編集2018/10/18 04:36
Zuishin

総合スコア28660

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

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

gaya-K

2018/10/16 10:55

横から失礼します。ほぼ同じ実装を考えました。が、このコードのセマフォは機能しなさそうなので DoSomething 全体を lock した方がいいと思います。
Zuishin

2018/10/16 11:35

本当ですね。ありがとうございました。 修正しました。
twck

2018/10/18 01:37

なるほど。SemaphoreSlimクラスで共有資源の管理ができるのですね。 参考にいたします。
twck

2018/10/18 03:42

buttonコントロールのclickイベントとしてユーザーの連打くらいなら問題なく動くと思うのですが、もし、button1_Clickを並列で2つ連続実行すると以下のような順番で処理される可能性がありませんか? 1番目の cts?.Cancel(); 2番目の cts?.Cancel(); 1番目の cts = new CancellationTokenSource(); 2番目の cts = new CancellationTokenSource(); こうなると2番目が1番目をキャンセルできずに semaphore.WaitAsync(); で待たされて、しばらく後に3番目が起動されても cts の中は2番目のCancellationTokenSourceなので1番目をキャンセルできないとかありませんか? 私がサンプルコードとして起動部分をbuttonコントロールのclickイベントとして書いてしまったのが悪いんですけど。
Zuishin

2018/10/18 03:49

ユーザーはそこまで素早く連打できないので省略しました。 必要であれば Cancel 以降 3 行をロックしてください。
twck

2018/10/18 04:12

ああ、やはりユーザーの操作を想定してのコードですよね。 へたにサンプルコードを書かなければよかった。 でも Cancel 直後からロックしても以下のようになる可能性がごくわずかに残るので、そのときは2番目は1番目をキャンセルできませんよね。 1番目の cts?.Cancel(); 2番目の cts?.Cancel(); cts == null なので Cancelは起こらない 1番目の semaphore.WaitAsync(); ロックして進む 2番目の semaphore.WaitAsync(); ロックされているので止まる とりあえず SemaphoreSlimクラス を教えていただいたので、これで自分でも考えてみます。
Zuishin

2018/10/18 04:13

直後ではなく、Cancel を含めたロックを意図しています。 ですから、現在の SemaphoreSlim と同じインスタンスは使えません。
twck

2018/10/18 04:34

ああ、SemaphoreSlimとは別のロック用オブジェクトを用意するということですね。 あと「Cancel 以降 3 行」と書いてあったので、Cancel(); と await semaphore.WaitAsync(); のあいだの以下の3行のことだと思ってCancel();は含めないのかと思ってしまいました。申し訳ありません。 cts = new CancellationTokenSource(); var token = cts.Token; return Task.Run(async () =>
Zuishin

2018/10/18 04:36

書き直しました。
guest

0

その非同期実行したいタスクのコードに、定期的にキャンセルをチェックして、キャンセルをされたら実行を終了するようなコードを組んでおきます
そして、あとに実行するコードの方に、キャンセルを通知して、実行終了を待つコードを組んでおきます

まあしかし、一つだけしか実行したくない、と言うならわざわざ別タスクにする必要はないのでは。

投稿2018/10/16 02:58

編集2018/10/16 03:05
y_waiwai

総合スコア87747

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

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

twck

2018/10/16 04:45 編集

メインスレッドとは非同期で実行する必要があるので別タスクにする必要があります。 提示したソースコードをご覧いただければ分かると思いますが、定期的なキャンセルのチェックと終了はCancellationTokenSourceを使用して出来ていると思います。 でも「その非同期実行したいタスクのコードに、定期的にキャンセルをチェックして、キャンセルをされたら実行を終了するようなコードを組んでおきます」とわざわざ御指摘なさってるということは、他にもっと良い方法があるのでしょうか? あと「実行するコードの方に、キャンセルを通知して、実行終了を待つコード」というのを具体的なコードとして教えていただけないでしょうか?
y_waiwai

2018/10/16 05:39 編集

そういうことなら、別タスクで実行したルーチン内で各関数を順番に実行していけば事が足りるのでは。 #そうすればタスク間でのゴタゴタは気にする必要がない 提示されたコードですが、あんまし詳しく見てませんが、 ・cancelTokenSourcesはlistにする必要はあるのか  task1が実行中、ってことはbutton1_Clickも実行中ってことなんで、これだけみてればいい? ・実行終了待ちに単純ループだとCPUが無駄。autoreseteventとか使えばどうでしょ ・lockには独立したオブジェクトを指定しよう ・await Task.Delay(1000); → Thread.sleep(1000); ぐらいでしょうか。
退会済みユーザー

退会済みユーザー

2018/10/16 11:09

twckさん、y_waiwaiさん、横から失礼します。 ・await Task.Delay(1000); → Thread.sleep(1000); というご指摘ですが、これは、何か処理をしているからawaitでなくスレッドを止めたほうがそれらしい という理解であっていますか?
twck

2018/10/18 01:48 編集

>cancelTokenSourcesはlistにする必要はあるのか 質問用のコードとして簡単に動作するようにbuttonコントロールのイベントで作ってあるのでアレなのですが、実際には button1_Clickの部分は並列実行されることもあるとしてください。 なので button1_Click を3つ以上同時に実行された場合、listにしてあるほうが都合が良かったのです。 >実行終了待ちに単純ループだとCPUが無駄。autoreseteventとか使えばどうでしょ なるほど。シグナル処理というものがあるのですね。調べてやってみます。 >lockには独立したオブジェクトを指定しよう すみません。質問用のコードとして要素を少なくしようとしたため、独立したlockオブジェクトは作りませんでした。 >await Task.Delay(1000); → Thread.sleep(1000); >何か処理をしているからawaitでなくスレッドを止めたほうがそれらしい という理解であっていますか? そうです。質問用のコードとして動かしたときにそれらしくスレッドを止めたかっただけなので、本当はDelayもSleepもしたいわけではなく、実際には重い処理が入るだけです。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問