2015年12月7日月曜日

C# で await/async を使ってみた。 何故か常に Task.IsCompleted == true になる。 動いているか、の判定に使えないので、困った。 dotNet4.5 で。

C# で await/async を使ってみた。 何故か常に Task.IsCompleted == true になる。 動いているか、の判定に使えないので、困った。 dotNet4.5 で。

// ----------------------------------------
// MainWindow.xaml.cs

//#define BY_THE_SLEEP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfAppTestOnDotNet45
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
       

        //
        // Task.IsCompleted が常に true を返すようです。
        // そのため完了しているか否かは volatile 変数などで手動で管理する必要がありそうです。
        //

        private Task taskForCancelTestAsync = null;
        private System.Threading.CancellationTokenSource ctsForCancelTestAsync = null;

        private void btnCancelForCancelTestAsync_Click(object sender, RoutedEventArgs e)
        {
            var tsk = taskForCancelTestAsync;
            var cts = ctsForCancelTestAsync;

            if (tsk == null || cts == null)
            {
                MessageBox.Show("実行されていません。");
                return;
            }
            else if (tsk != null && (tsk.IsCanceled || /* tsk.IsCompleted || */ tsk.IsFaulted))
            {
                // 常に tsk.IsCompleted == true になります。
                // また tsk.Status == TaskStatus.RanToCompletion になっています。
                // よって
                // Cancel 出来るか、とか、Runできるか、といった判定に使えないようです。
                MessageBox.Show("既に完了しています。");
                return;
            }
            else if (cts != null && cts.IsCancellationRequested)
            {
                MessageBox.Show("キャンセル中です。");
                return;
            }
            else
            {
                //MessageBox.Show("実行中です。");
                //return;
            }

            try
            {
                var syncContextUI = System.Threading.Tasks.TaskScheduler.FromCurrentSynchronizationContext();

#if true
                tsk.ContinueWith((curTask) => {
                    MessageBox.Show("キャンセルできました。");
                }, syncContextUI);
                cts.Cancel();
#endif
#if false
                // これは「Cancel 時に実行」という意図があるが、MessageBox.Showが動かない。
                var cts2nd = new System.Threading.CancellationTokenSource();
                var ctoken2nd = cts2nd.Token;
                tsk.ContinueWith((curTask) => {
                    MessageBox.Show("キャンセルできました。[CancelOnly]");
                }, ctoken2nd, TaskContinuationOptions.OnlyOnCanceled, syncContextUI);
                cts.Cancel();
#endif
            }
            catch (TaskCanceledException ex)
            {
                // たぶんキャンセルする側のスレッドで呼ばれる例外。
                //      Sleep のときも Task.Delay のときも呼ばれなかった。
                MessageBox.Show("[btnCancel_Click]Error: TaskCanceledException");
                Console.WriteLine("[btnCancel_Click]TaskCanceledException:" + ex.ToString());
            }
            catch (OperationCanceledException ex)
            {
                // たぶんキャンセルされたスレッドで呼ばれる例外。
                //      Sleep のときも Task.Delay のときも呼ばれなかった。
                MessageBox.Show("[btnCancel_Click]Error: OperationCanceledException");
                Console.WriteLine("[btnCancel_Click]OperationCanceledException:" + ex.ToString());
            }
            catch (AggregateException exg)
            {
                //      Sleep のときも Task.Delay のときも呼ばれなかった。
                MessageBox.Show("[btnCancel_Click]Error: AggregateException");
                Console.WriteLine("[btnCancel_Click]AggregateException:" + exg.ToString());
                foreach (Exception ex in exg.InnerExceptions)
                {
                    Console.WriteLine("\t" + ex.ToString());
                }
            }
            catch (Exception ex)
            {
                //      Sleep のときも Task.Delay のときも呼ばれなかった。
                MessageBox.Show("[btnCancel_Click]Error: OperationCanceledException");
                Console.WriteLine("[btnCancel_Click]Exception:" + ex.ToString());
                throw;
            }

        }

        private void btnRunForCancelTestAsync_Click(object sender, RoutedEventArgs e)
        {
            var tsk = taskForCancelTestAsync;
            var cts = ctsForCancelTestAsync;

            if (tsk == null || cts == null)
            {
                //MessageBox.Show("実行されていません。");
                //return;
            }
            else if (tsk != null && (tsk.IsCanceled || tsk.IsCompleted || tsk.IsFaulted))
            {
                //MessageBox.Show("既に完了しています。");
                //return;
            }
            else if (cts != null && cts.IsCancellationRequested)
            {
                MessageBox.Show("キャンセル中です。");
                return;
            }
            else
            {
                MessageBox.Show("実行中です。");
                return;
            }

            if (tsk != null)
            {
                tsk.Dispose();
                tsk = null;
            }
            if (cts != null)
            {
                cts.Dispose();
                cts = null;
            }

            taskForCancelTestAsync = null;
            ctsForCancelTestAsync = null;

            try
            {

            }
            catch (Exception ex)
            {
                MessageBox.Show("[btnRun_Click]Error: OperationCanceledException");
                Console.WriteLine("[btnRun_Click]Exception:" + ex.ToString());
                throw;
            }
            ctsForCancelTestAsync = new System.Threading.CancellationTokenSource();
            var ctoken = ctsForCancelTestAsync.Token;
#if BY_THE_SLEEP
            taskForCancelTestAsync = System.Threading.Tasks.Task.Factory.StartNew(
                () =>
                {
                    CancelTestForAsync.Run(ctoken);
                }, ctoken);
#else
            taskForCancelTestAsync = System.Threading.Tasks.Task.Factory.StartNew(
                async () =>
                {
                    await CancelTestForAsync.Run(ctoken);
                }, ctoken);
#endif
        }
    }
}

// ----------------------------------------
// CancelTestForAsync.cs

//#define BY_THE_SLEEP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfAppTestOnDotNet45
{
    class CancelTestForAsync
    {
#if BY_THE_SLEEP
        public static void Run(System.Threading.CancellationToken ctoken) // ← Sleep の場合の定義。
#else
        public static async Task Run(System.Threading.CancellationToken ctoken) // ← Delay の場合の定義。
#endif
        {
            Console.WriteLine("[CancelTestForAsync::Run]Start");
            for (int i = 0; i < 20; i++)
            {
                try
                {
#if BY_THE_SLEEP
                    System.Threading.Thread.Sleep(millisecondsTimeout: 20 * 1000);
#else
                    //     Task.Delay(millisecondsDelay: 20 * 1000, cancellationToken: ctoken);
                    // のように await が無い場合は、別スレッドを実行した後、すぐに続行するので意味が無いことに注意。
                    await Task.Delay(millisecondsDelay: 20 * 1000, cancellationToken: ctoken);
#endif
                    if (ctoken.IsCancellationRequested)
                    {
                        // Sleep の場合、ここに来る。 System.Threading.CancellationToken では Cancel 即座のキャンセルが出来ない。
                        Console.WriteLine("[CancelTestForAsync::Run]ctoken.ThrowIfCancellationRequested();");
                        ctoken.ThrowIfCancellationRequested();
                    }
                    Console.WriteLine("[CancelTestForAsync::Run]Delayed");
                }
                catch (TaskCanceledException ex)
                {
                    // たぶんキャンセルする側のスレッドで呼ばれる例外。
                    //      Sleep の場合、想定どおり、ここに来ない。
                    //      Task.Delay の場合、ここに来る。 await の中で Cancel が検知されると、ここに来るということ。
                    Console.WriteLine("[CancelTestForAsync::Run]TaskCanceledException:" + ex.ToString());
                    throw;
                }
                catch (OperationCanceledException ex)
                {
                    // たぶんキャンセルされたスレッドで呼ばれる例外。
                    //      Sleep の場合、想定どおり、ここで発生。 ctoken.ThrowIfCancellationRequested(); で呼ばれる、ということ。
                    Console.WriteLine("[CancelTestForAsync::Run]OperationCanceledException:" + ex.ToString());
                    throw;
                }
                catch (AggregateException exg)
                {
                    // Sleep の場合、想定どおり、ここに来ない。
                    Console.WriteLine("[CancelTestForAsync::Run]AggregateException:" + exg.ToString());
                    foreach (Exception ex in exg.InnerExceptions)
                    {
                        Console.WriteLine("\t" + ex.ToString());
                    }
                    throw;
                }
                catch (Exception ex)
                {
                    // Sleep の場合、想定どおり、ここに来ない。
                    Console.WriteLine("[CancelTestForAsync::Run]Exception:" + ex.ToString());
                    throw;
                }
            }
        }
    }
}

// ----------------------------------------

0 件のコメント:

コメントを投稿