C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Taskが完了するまで待つには

[トピック内 20 記事 (1 - 20 表示)]  << 0 >>

■85389 / inTopicNo.1)  Taskが完了するまで待つには
  
□投稿者/ 炒飯 (1回)-(2017/10/17(Tue) 10:28:42)

分類:[C#] 

お世話になります。
現在、C#の勉強をしていますが、Taskについて分からないことがあるので質問させて頂きます。
環境はVisualStuidio2015を使っています。

はじめに、リストボックスとボタンが一つずつあるフォームを作成しました。
フォームのコードは以下のものです。
ボタンを押すと、合計10秒かかる計算処理を非同期で行い、その結果がフィールドの変数に保存されます。
また、計算の合間にリストボックスに「計算開始」「計算中」「計算終了」の文字が追加されます。

ここで質問なのですが、計算ボタンを押して計算処理のタスクを実行している最中に、
再び計算ボタンを押したとき、あるいはフォームを閉じようとしたときに、そのタスクが完了するまで待つにはどうすればよいのでしょうか?
タスクの処理の中でフォームのコントロールを更新するためにDeligateを使っているため、Task.Wait()をするとフリーズしてしまいます。
ご教授お願いします。



//フォームのコード

using System;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Sample
{
	public partial class Form1 : Form
	{
		private delegate void WriteDeligate(String text);
		private int calculateResult = 0;
		Task taskCalculate = null;

		public Form1()
		{
			InitializeComponent();
		}

		//計算を開始するボタンを押したときの処理
		private void button1_Click(object sender, EventArgs e)
		{
			//ここで、既に実行しているタスクがあるなら、そのタスクが終了するまで待ちたい
			if(taskCalculate != null) {
				//Waitをするとフリーズしてしまう
				//taskCalculate.Wait();
			}

			//計算を始める
			taskCalculate = Task.Run(Calculate);
		}

		//10秒かかる計算処理
		private async Task Calculate()
		{
			Invoke(new WriteDeligate(WriteText), "計算開始");

			//時間のかかる処理その1
			await Task.Delay(5000);
			Invoke(new WriteDeligate(WriteText), "計算中");

			//時間のかかる処理その2
			await Task.Delay(5000);
			Invoke(new WriteDeligate(WriteText), "計算終了");

			//計算結果をフィールド変数に保存する(これは適当な値です)
			calculateResult = 999;
		}

		//リストボックスにテキストを追加する
		private void WriteText(String text)
		{
			listBox1.Items.Add(text);
		}

		//フォームが閉じられるときの処理
		private void Form1_FormClosing(object sender, FormClosingEventArgs e)
		{
			//ここで、既に実行しているタスクがあるなら、そのタスクが終了するまで待ちたい
			if(taskCalculate != null) {
				//Waitをするとフリーズしてしまう
				//taskCalculate.Wait();
			}
		}
	}
}

引用返信 編集キー/
■85393 / inTopicNo.2)  Re[1]: Taskが完了するまで待つには
□投稿者/ とっちゃん (466回)-(2017/10/17(Tue) 11:46:39)
No85389 (炒飯 さん) に返信
> はじめに、リストボックスとボタンが一つずつあるフォームを作成しました。
> フォームのコードは以下のものです。
> ボタンを押すと、合計10秒かかる計算処理を非同期で行い、その結果がフィールドの変数に保存されます。
> また、計算の合間にリストボックスに「計算開始」「計算中」「計算終了」の文字が追加されます。
>
> ここで質問なのですが、計算ボタンを押して計算処理のタスクを実行している最中に、
> 再び計算ボタンを押したとき、あるいはフォームを閉じようとしたときに、そのタスクが完了するまで待つにはどうすればよいのでしょうか?
> タスクの処理の中でフォームのコントロールを更新するためにDeligateを使っているため、Task.Wait()をするとフリーズしてしまいます。

計算の非同期処理は、中断可能なコードでしょうか?
サンプルコードの場合だと Task.Delay() 部分は中断できませんが
それぞれのInvokeの合間合間であれば、中断処理を挟むことができます。

これはあくまでもサンプルなので、実際のコードは中断できるようにはなっていないかもしれませんが
計算ボタンをもう一度押したあるいはフォームを閉じるなどであれば、処理結果は必要ないと思われます。

中断可能な形で非同期処理を実装できるのであれば、CancellationToken などを利用して
処理を中断できるように実装し、呼び出し側は、中断を通知して、タスクの完了を待つ(Task.Waitする)ことで
デッドロックを発生しないようにできます(中断処理中は、フォームのコントロール更新はしない)。

もし、中断できる実装ではないという場合でも、フォームの更新処理のタイミングであればユーザーコードが走ると思います。
そのタイミングで、上記フラグを見て、Invokeしないようにすれば、デッドロックは回避できます。

引用返信 編集キー/
■85394 / inTopicNo.3)  Re[1]: Taskが完了するまで待つには
□投稿者/ shu (1058回)-(2017/10/17(Tue) 13:22:28)
No85389 (炒飯 さん) に返信

BackgroundWorkerを使われた方が実装しやすいです。
以下VB.NETのサンプルです。

BackgroundWorkerをフォームに貼付けbgwという名前にします。



'Private変数を用意(Formクローズ制御用)
Private _FormClose As Boolean

'DoWorkイベント実装ここでメイン処理を行う
Private Sub bgw_DoWork(sender As Object, e As DoWorkEventArgs) Handles bgw.DoWork
    〜今 Task で実行している処理
End Sub

'完了時の処理
Private Sub bgw_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles bgw.RunWorkerCompleted
    If _FormClose Then
        Me.Close
    End If
End Sub




'処理の開始
_FormClose = False
bgw.RunWorkerAsync()


'Form クロージングイベント
Private Sub Me_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
    If bgw.IsBusy Then
        _FormClose = True
        e.Cancel = True
    End If
End Sub

引用返信 編集キー/
■85395 / inTopicNo.4)  Re[2]: Taskが完了するまで待つには
□投稿者/ 炒飯 (2回)-(2017/10/17(Tue) 15:53:42)
ご回答ありがとうございます。
先のコードは中断可能なものでした。説明が足りなくてすみません。
ご指摘頂いたようにInvokeの直前に中断要求のフラグを確認し、要求があればInvokeしないという処理を加えることで、
再度ボタンを押したときやフォームを閉じたときに、タスクが終了するまでウェイトするということが実現できました。

なのですが、次のコードを試した場合、フリーズが起きてしまいました。

このコードは次のようなことを目的としています。
はじめに、ボタンを押すと計算のタスクが実行されます。
このタスクは、100ミリ秒ずつウェイトしながらリストに「計算中」というテキストを追加する処理を50回繰り返すものです。
再びボタンを押すと、そのタスクに中断要求が送られ、そのタスクが終了するまでウェイトされます。
タスクは中断要求を受けると直ちに終了するようになっているので、すぐにタスクは終了し、ウェイトも解除されます。
同様に、フォームを閉じたときもタスクが終了するまでウェイトしてほしいので、ボタンを押したときと同じ処理を書いています。

このコードを実際に実行し、ボタンを押して計算のタスクを実行させました。
そして、そのタスクが終わる前に再びボタンを押すと、たしかにタスクは中断され、タスクが終了するまでウェイトすることが分かりました。
ですが、タスクを実行中にフォームの×ボタンを押した場合、フリーズしてしまいました。

なぜ、再度ボタンを押した場合と異なり、フォームが閉じられる場合にはフリーズしてしまうのでしょうか。
また、フォームを閉じるときもフリーズしないでタスクが終了するまでウェイトするにはどうすればよいでしょうか。
ご教授頂けたら幸いです。



//フォームのコード

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace Sample
{
	public partial class Form1 : Form
	{
		private delegate void WriteDeligate(String text);
		private int calculateCount = 0;
		Task taskCalculate = null;
		//計算処理を中断するためのトークン
		CancellationTokenSource cancelSource = null;

		public Form1()
		{
			InitializeComponent();
		}

		//計算を開始するボタンを押したときの処理
		private void button1_Click(object sender, EventArgs e)
		{
			//既に実行しているタスクがあるなら、そのタスクに中断要求を出し、中断が完了するまで待つ
			if(taskCalculate != null) {
				WaitUntilTaskEnd();
				//中断時の計算回数を表示する
				Console.WriteLine("計算回数 = " + calculateCount.ToString());

			} else {
				//計算を中断するためのトークンを設定
				cancelSource = new CancellationTokenSource();
				//計算を始める
				taskCalculate = Task.Run(Calculate, cancelSource.Token);
			}
		}

		//非同期で行いたい計算と表示の処理
		private async Task Calculate()
		{
			//中断要求を見るためのトークン
			CancellationToken token = cancelSource.Token;
			//計算回数を初期化
			calculateCount = 0;

			try {
				token.ThrowIfCancellationRequested();
				Invoke(new WriteDeligate(WriteText), "計算開始");

				//繰り返す必要のある計算処理
				for(int i = 0; i < 50; i ++) {
					token.ThrowIfCancellationRequested();
					Invoke(new WriteDeligate(WriteText), "計算中");
					//なんらかの計算処理
					await Task.Delay(100);
					calculateCount ++;
				}
				
				token.ThrowIfCancellationRequested();
				Invoke(new WriteDeligate(WriteText), "計算終了");

				Console.WriteLine("計算処理が完了した");

			}catch(OperationCanceledException) {
				Console.WriteLine("計算処理を中断した");
			}
		}

		//リストボックスにテキストを追加する
		private void WriteText(String text)
		{
			listBox1.Items.Add(text);
		}

		//フォームが閉じられるときの処理
		private void Form1_FormClosing(object sender, FormClosingEventArgs e)
		{
			//既に実行しているタスクがあるなら、そのタスクに中断要求を出し、中断が完了するまで待つ
			if(taskCalculate != null) {
				WaitUntilTaskEnd();
				//中断時の計算回数を表示する
				Console.WriteLine("計算回数 = " + calculateCount.ToString());
			}
		}

		//計算のタスクが実行中なら、終わるまで待つ
		private void WaitUntilTaskEnd()
		{
			//中断要求を送る
			cancelSource.Cancel();
			//タスクが中断して終わるまで待つ
			taskCalculate.Wait();
			//タスクが終了したので初期化
			taskCalculate = null;
		}
	}
}

引用返信 編集キー/
■85396 / inTopicNo.5)  Re[2]: Taskが完了するまで待つには
□投稿者/ 炒飯 (3回)-(2017/10/17(Tue) 15:57:09)
No85394 (shu さん) に返信
すみません。ちょうど返信があったときに私が本文を書いていたようで、見ていませんでした。
BackgroundWorkerについてですが、試してみようと思います。
引用返信 編集キー/
■85397 / inTopicNo.6)  Re[3]: Taskが完了するまで待つには
□投稿者/ とっちゃん (467回)-(2017/10/17(Tue) 16:42:37)
No85395 (炒飯 さん) に返信
> このコードを実際に実行し、ボタンを押して計算のタスクを実行させました。
> そして、そのタスクが終わる前に再びボタンを押すと、たしかにタスクは中断され、タスクが終了するまでウェイトすることが分かりました。
> ですが、タスクを実行中にフォームの×ボタンを押した場合、フリーズしてしまいました。
>
フォームの×ボタンを押したとき、どこでフリーズしていますか?

UIスレッド(メインスレッド)は、WaitUntilTaskEnd() の taskCalculate.Wait(); で待機しているでしょうか?
スレッド側はどこで止まってしまうのでしょう?

もしかしたら見当違いのところを見ているのかもしれません(わかりませんが)。

一見すると問題ないように見えても、非同期に動いている結果、
意図していなかった動作になっているということもあります。

どこで止まっているかがわかれば、ある程度は解決のヒントになるかもしれません。
(動かせるコードがないと回答側は現象を見ることはできませんのでご自身で調査する必要があります)

引用返信 編集キー/
■85398 / inTopicNo.7)  Re[4]: Taskが完了するまで待つには
□投稿者/ 炒飯 (4回)-(2017/10/17(Tue) 18:45:12)
2017/10/17(Tue) 18:46:31 編集(投稿者)
No85397 (とっちゃん さん) に返信

先ほどのコードにConsole.WriteLineを入れ、どこで停止しているかを調べてみました。
(■のついているところです)
計算中に×ボタンを押してフリーズさせたとき、出力は次のようになっていました。


■Invoke_Start
■WriteText_Start
■WriteText_End
■Invoke_End
■Invoke_Start
■Form_Close_Start
■WaitUntilTaskEnd_Start
■Task_Cancel_Start
■Task_Cancel_End
■Task_Wait_Start
(ここでフリーズ)


これを見ると、「Invoke_Start」の後に表示されるはずの「WriteText_Start」がなく、
かわりに「Form_Close_Start」が表示されています。
これは、Invokeの行は実行したが、そのInvokeで指定された関数が呼ばれる前に、
フォームが閉じられる処理に入ってしまったということでしょうか。
それならそのままタスクのWaitが行われるので、Invokeされた関数が実行できず、フリーズしてしまうと理解できます。

Invokeを使ったら、UIスレッドの方では直ちに指定した関数が実行されると思ったのですが、これを見る限り違うようです。
ですが、ではこれはどうしたら解決できるのでしょうか。
それとも、No85394 のshu さんのコードのように、フォームが閉じられるときのイベントではフォームを閉じず、、
別に用意したフラグをtrueにして、
タスクの終わりにフォームを閉じる処理を書いた方が適切なのでしょうか。
よろしくお願いします。



//フォームのコード

using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace Sample
{
	public partial class Form1 : Form
	{
		private delegate void WriteDeligate(String text);
		private int calculateCount = 0;
		Task taskCalculate = null;
		//計算処理を中断するためのトークン
		CancellationTokenSource cancelSource = null;

		public Form1()
		{
			InitializeComponent();
		}

		//計算を開始するボタンを押したときの処理
		private void button1_Click(object sender, EventArgs e)
		{
			//既に実行しているタスクがあるなら、そのタスクに中断要求を出し、中断が完了するまで待つ
			if(taskCalculate != null) {
				WaitUntilTaskEnd();
				//中断時の計算回数を表示する
				Console.WriteLine("計算回数 = " + calculateCount.ToString());

			} else {
				//計算を中断するためのトークンを設定
				cancelSource = new CancellationTokenSource();
				//計算を始める
				taskCalculate = Task.Run(Calculate, cancelSource.Token);
			}
		}

		//非同期で行いたい計算と表示の処理
		private async Task Calculate()
		{
			//中断要求を見るためのトークン
			CancellationToken token = cancelSource.Token;
			//計算回数を初期化
			calculateCount = 0;

			try {
				token.ThrowIfCancellationRequested();
				Invoke(new WriteDeligate(WriteText), "計算開始");

				//繰り返す必要のある計算処理
				for(int i = 0; i < 50; i ++) {
					token.ThrowIfCancellationRequested();

					Console.WriteLine("■Invoke_Start");
					Invoke(new WriteDeligate(WriteText), "計算中");
					Console.WriteLine("■Invoke_End");

					//なんらかの計算処理
					await Task.Delay(100);
					calculateCount ++;
				}
				
				token.ThrowIfCancellationRequested();
				Invoke(new WriteDeligate(WriteText), "計算終了");

				Console.WriteLine("計算処理が完了した");

			}catch(OperationCanceledException) {
				Console.WriteLine("計算処理を中断した");
			}
		}

		//リストボックスにテキストを追加する
		private void WriteText(String text)
		{
			Console.WriteLine("■WriteText_Start");
			listBox1.Items.Add(text);
			Console.WriteLine("■WriteText_End");
		}

		//フォームが閉じられるときの処理
		private void Form1_FormClosing(object sender, FormClosingEventArgs e)
		{
			Console.WriteLine("■Form_Close_Start");
			//既に実行しているタスクがあるなら、そのタスクに中断要求を出し、中断が完了するまで待つ
			if(taskCalculate != null) {
				Console.WriteLine("■WaitUntilTaskEnd_Start");
				WaitUntilTaskEnd();
				Console.WriteLine("■WaitUntilTaskEnd_End");

				//中断時の計算回数を表示する
				Console.WriteLine("計算回数 = " + calculateCount.ToString());
			}
			Console.WriteLine("■Form_Close_End");
		}

		//計算のタスクが実行中なら、終わるまで待つ
		private void WaitUntilTaskEnd()
		{
			//中断要求を送る
			Console.WriteLine("■Task_Cancel_Start");
			cancelSource.Cancel();
			Console.WriteLine("■Task_Cancel_End");

			//タスクが中断して終わるまで待つ
			Console.WriteLine("■Task_Wait_Start");
			taskCalculate.Wait();
			Console.WriteLine("■Task_Wait_End");

			//タスクが終了したので初期化
			taskCalculate = null;
		}
	}
}

引用返信 編集キー/
■85399 / inTopicNo.8)  Re[5]: Taskが完了するまで待つには
□投稿者/ Azulean (882回)-(2017/10/17(Tue) 23:58:36)
No85398 (炒飯 さん) に返信
> Invokeを使ったら、UIスレッドの方では直ちに指定した関数が実行されると思ったのですが、これを見る限り違うようです。

違いますね。
Invoke はあくまで「処理をウィンドウメッセージ経由でお願いし、完了を待つ」だけであり、即座に実行されることを保証していません。
他方、メインスレッドがウィンドウメッセージを受け取れる状態にない場合は、デッドロック(開放されない待ち)に陥ります。

> ですが、ではこれはどうしたら解決できるのでしょうか。

Wait を使わないことです。

例としては、FormClosing でタスク実行中であることを検出したら、そのタスクに対して ContinueWith で終わったとき / キャンセル完了したとき / エラーになったときにフォームを閉じる BeginInvoke を実行を予約しておき、FormClosing 自体はキャンセルした方が良いでしょうね。
コード的にはいろいろとやり方はあると思いますが、FormClosing で「待つ」という方式では、どこかに弱点が残るので解決不可能です。
引用返信 編集キー/
■85401 / inTopicNo.9)  Re[6]: Taskが完了するまで待つには
□投稿者/ 炒飯 (5回)-(2017/10/18(Wed) 08:03:44)
このような状況では、メインスレッド内でタスク終了を待つことはできないのですね。
大変よく分かりました。これにて質問は解決したいと思います。
回答してくださった皆様、本当にありがとうございました。
解決済み
引用返信 編集キー/
■85402 / inTopicNo.10)  Re[7]: Taskが完了するまで待つには
□投稿者/ shu (1059回)-(2017/10/18(Wed) 08:30:11)
No85401 (炒飯 さん) に返信
> このような状況では、メインスレッド内でタスク終了を待つことはできないのですね。
> 大変よく分かりました。これにて質問は解決したいと思います。
> 回答してくださった皆様、本当にありがとうございました。
待つことができないわけではなくて待ち状態になるのでタスクが終わるまで
UIへのアクセスが出来なくなっているのです。フリーズではなく終わるのを待っているのです。
フリーズっぽく見えなくするには、処理中タスクが終わった後に行う処理をキューイングして
UIスレッドでの待ちを少なくしタスクが終わった後にキューイングされている処理を実行することが
必要です。キューイングする処理を増やしすぎるとボタンをクリックしすぎたときに次々処理が
行われてしまうので、タスク実行中はボタンを使用できないようにした方がスマートかもしれません。
解決済み
引用返信 編集キー/
■85406 / inTopicNo.11)  Re[8]: Taskが完了するまで待つには
□投稿者/ Azulean (883回)-(2017/10/18(Wed) 22:54:12)
No85402 (shu さん) に返信
> フリーズっぽく見えなくするには、処理中タスクが終わった後に行う処理をキューイングして
> UIスレッドでの待ちを少なくしタスクが終わった後にキューイングされている処理を実行することが
> 必要です。キューイングする処理を増やしすぎるとボタンをクリックしすぎたときに次々処理が
> 行われてしまうので、タスク実行中はボタンを使用できないようにした方がスマートかもしれません。

Form1_FormClosing → WaitUntilTaskEnd → Task.Wait と呼ばれているので、Invoke との順番次第でデッドロックするという話だと、私は捉えています。
解決済み
引用返信 編集キー/
■85412 / inTopicNo.12)  Re[9]: Taskが完了するまで待つには
□投稿者/ shu (1060回)-(2017/10/19(Thu) 11:00:53)
No85406 (Azulean さん) に返信
> ■No85402 (shu さん) に返信
>>フリーズっぽく見えなくするには、処理中タスクが終わった後に行う処理をキューイングして
>>UIスレッドでの待ちを少なくしタスクが終わった後にキューイングされている処理を実行することが
>>必要です。キューイングする処理を増やしすぎるとボタンをクリックしすぎたときに次々処理が
>>行われてしまうので、タスク実行中はボタンを使用できないようにした方がスマートかもしれません。
>
> Form1_FormClosing → WaitUntilTaskEnd → Task.Wait と呼ばれているので、Invoke との順番次第でデッドロックするという話だと、私は捉えています。
すみません、確かにそういう事でした。フリーズの下りは除外してください。

引用返信 編集キー/
■85415 / inTopicNo.13)  Re[3]: Taskが完了するまで待つには
□投稿者/ なちゃ (220回)-(2017/10/19(Thu) 15:50:45)
今さらですが、そもそもawaitを活用するならInvokeする必要はありません。
というかこう言うのをシンプルにやるためのawaitです。

携帯から手打ちなのでミスしてる可能性が結構ありますが、イメージとしてはこんな感じです。

private int calculateResult = 0;
Task taskCalculate = null;

public Form1()
{
InitializeComponent();
}

//計算を開始するボタンを押したときの処理
private async void button1_Click(object sender, EventArgs e)
{
//ここで、既に実行しているタスクがあるなら、そのタスクが終了するまで待ちたい
if(taskCalculate != null) {
//Waitをするとフリーズしてしまう
await taskCalculate;
}

//計算を始める
taskCalculate = Calculate();
}

//10秒かかる計算処理
private async Task Calculate()
{
WriteText("計算開始");

//時間のかかる処理その1
await Task.Delay(5000);
WriteText("計算中");

//時間のかかる処理その2
await Task.Delay(5000);
WriteText("計算終了");

//計算結果をフィールド変数に保存する(これは適当な値です)
calculateResult = 999;
}

//リストボックスにテキストを追加する
private void WriteText(String text)
{
listBox1.Items.Add(text);
}

//フォームが閉じられるときの処理
private async void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
//ここで、既に実行しているタスクがあるなら、そのタスクが終了するまで待ちたい
if(taskCalculate != null) {
//Waitをするとフリーズしてしまう
await taskCalculate;
}
}
引用返信 編集キー/
■85416 / inTopicNo.14)  Re[4]: Taskが完了するまで待つには
□投稿者/ なちゃ (221回)-(2017/10/19(Thu) 15:54:19)
No85415 (なちゃ さん) に返信
ごめんなさい、図表モードにするの忘れてました。
削除できないのでちょっと書き直し。


		private int calculateResult = 0;
		Task taskCalculate = null;

		public Form1()
		{
			InitializeComponent();
		}

		//計算を開始するボタンを押したときの処理
		private async void button1_Click(object sender, EventArgs e)
		{
			//ここで、既に実行しているタスクがあるなら、そのタスクが終了するまで待ちたい
			if(taskCalculate != null) {
				//Waitをするとフリーズしてしまう
				await taskCalculate;
			}

			//計算を始める
			taskCalculate = Calculate();
		}

		//10秒かかる計算処理
		private async Task Calculate()
		{
			WriteText("計算開始");

			//時間のかかる処理その1
			await Task.Delay(5000);
			WriteText("計算中");

			//時間のかかる処理その2
			await Task.Delay(5000);
			WriteText("計算終了");

			//計算結果をフィールド変数に保存する(これは適当な値です)
			calculateResult = 999;
		}

		//リストボックスにテキストを追加する
		private void WriteText(String text)
		{
			listBox1.Items.Add(text);
		}

		//フォームが閉じられるときの処理
		private async void Form1_FormClosing(object sender, FormClosingEventArgs e)
		{
			//ここで、既に実行しているタスクがあるなら、そのタスクが終了するまで待ちたい
			if(taskCalculate != null) {
				//Waitをするとフリーズしてしまう
				await taskCalculate;
			}
		}

引用返信 編集キー/
■85417 / inTopicNo.15)  Re[5]: Taskが完了するまで待つには
□投稿者/ なちゃ (222回)-(2017/10/19(Thu) 15:56:47)
あと、クリックイベントの最後でもawaitして、その後でタスクの変数にnull入れる方がいいですね、多分。
引用返信 編集キー/
■85418 / inTopicNo.16)  Re[6]: Taskが完了するまで待つには
□投稿者/ なちゃ (223回)-(2017/10/19(Thu) 16:13:59)
No85417 (なちゃ さん) に返信
awaitするタスクの予約が2つ以上になると、一気に開始してしまうので、最初のifはwhileにする必要がありますね。

あとクローズ待ちは、いったんキャンセルしてawait後に再度Closeを呼ぶなどの工夫が必要ですね。
引用返信 編集キー/
■85421 / inTopicNo.17)  Re[7]: Taskが完了するまで待つには
□投稿者/ なちゃ (224回)-(2017/10/19(Thu) 16:41:55)
まとめるとこんな感じ。
ただしクローズ待ちは若干タイミング依存の不安あり(awaitで起きる順番によって若干の不安あり)



private int calculateResult = 0;
Task taskCalculate = null;

public Form1()
{
InitializeComponent();
}

//計算を開始するボタンを押したときの処理
private async void button1_Click(object sender, EventArgs e)
{
//ここで、既に実行しているタスクがあるなら、そのタスクが終了するまで待ちたい
while (taskCalculate != null) {
//Waitをするとフリーズしてしまう
await taskCalculate;
}

//計算を始める
taskCalculate = Calculate();
await taskCalculate;
taskCalculate = null;
}

//10秒かかる計算処理
private async Task Calculate()
{
WriteText("計算開始");

//時間のかかる処理その1
await Task.Delay(5000);
WriteText("計算中");

//時間のかかる処理その2
await Task.Delay(5000);
WriteText("計算終了");

//計算結果をフィールド変数に保存する(これは適当な値です)
calculateResult = 999;
}

//リストボックスにテキストを追加する
private void WriteText(String text)
{
listBox1.Items.Add(text);
}

//フォームが閉じられるときの処理
private async void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
//ここで、既に実行しているタスクがあるなら、そのタスクが終了するまで待ちたい
while (taskCalculate != null) {
//Waitをするとフリーズしてしまう
e.Cancel = true;
await taskCalculate;
}
if (e.Cancel) Close();
}

引用返信 編集キー/
■85422 / inTopicNo.18)  Re[8]: Taskが完了するまで待つには
□投稿者/ なちゃ (225回)-(2017/10/19(Thu) 16:47:13)
入力ミスして入れ直したせいでまた図表モードがオフに…
ごめんなさいやり直し…

		private int calculateResult = 0;
		Task taskCalculate = null;

		public Form1()
		{
			InitializeComponent();
		}

		//計算を開始するボタンを押したときの処理
		private async void button1_Click(object sender, EventArgs e)
		{
			//ここで、既に実行しているタスクがあるなら、そのタスクが終了するまで待ちたい
			while (taskCalculate != null) {
				//Waitをするとフリーズしてしまう
				await taskCalculate;
			}

			//計算を始める
			taskCalculate = Calculate();
                        await taskCalculate;
                        taskCalculate = null;
		}

		//10秒かかる計算処理
		private async Task Calculate()
		{
			WriteText("計算開始");

			//時間のかかる処理その1
			await Task.Delay(5000);
			WriteText("計算中");

			//時間のかかる処理その2
			await Task.Delay(5000);
			WriteText("計算終了");

			//計算結果をフィールド変数に保存する(これは適当な値です)
			calculateResult = 999;
		}

		//リストボックスにテキストを追加する
		private void WriteText(String text)
		{
			listBox1.Items.Add(text);
		}

		//フォームが閉じられるときの処理
		private async void Form1_FormClosing(object sender, FormClosingEventArgs e)
		{
			//ここで、既に実行しているタスクがあるなら、そのタスクが終了するまで待ちたい
			while (taskCalculate != null) {
				//Waitをするとフリーズしてしまう
				await taskCalculate;
                                e.Cancel = true;
			}
                        if (e.Cancel) Close();
		}

引用返信 編集キー/
■85423 / inTopicNo.19)  Re[9]: Taskが完了するまで待つには
□投稿者/ なちゃ (226回)-(2017/10/19(Thu) 16:54:16)
> 		//フォームが閉じられるときの処理
> 		private async void Form1_FormClosing(object sender, FormClosingEventArgs e)
> 		{
> 			//ここで、既に実行しているタスクがあるなら、そのタスクが終了するまで待ちたい
> 			while (taskCalculate != null) {
> 				//Waitをするとフリーズしてしまう
> 				await taskCalculate;
>                                 e.Cancel = true;
> 			}
>                         if (e.Cancel) Close();
> 		}

図表モードで入力し直したときに一カ所ミス。
この二行は順番が逆です。

> 				await taskCalculate;
>                                 e.Cancel = true;

e.Cancel = true;
の後でawaitです。


引用返信 編集キー/
■85438 / inTopicNo.20)  Re[10]: Taskが完了するまで待つには
 
□投稿者/ なちゃ (227回)-(2017/10/20(Fri) 13:58:02)
ついでなので少し補足です。
もしどうしてもCalculate自体を丸ごと非同期に実行したいなら、表示にはInvokeではなくBeginInvokeをつかってください。
※いや、ほんとはそれもベストではないですが
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ