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;
}
}
}