HeyCHのブログ

慢性疲労のへいちゃんです

【C#】async/await Taskについて

僕は意図的に「async/await Task」を使用するのを避けてきました。
何でかっていうと、難しいから。この一言に尽きます。
しかし、今回はその表層部分のみをしれっと書いていきたいと思います。
僕は今でも非同期処理はBackgroundWorkerで良いじゃん!って思います。


さて、それでは、非同期処理がなぜ必要なのか見ていくことにしましょう。
まずは以下のような感じでWindowsフォームアプリケーションを作成します。
f:id:HeyCH:20200515235356p:plain
※Cancelはあとで使います。
それに以下のようなコードを書き、Startを押すと×ボタンすら押せなくなり、何もできなくなります。

    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e) {
            HeavyMethod();
        }

        private void HeavyMethod() {
            while (true) {
                System.Threading.Thread.Sleep(1000);
            }
        }
    }

しかし、これをTaskを使って実行してやると、Taskが別スレッドで実行され、ボタンが押せるようになります。
そこで、キャンセルも簡単に実装できるやん!って書くとこうなります。

        public Form1() {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e) {
            Task.Run(()=>HeavyMethod());
            if (cancel) {
                MessageBox.Show("キャンセルしました。");
            } else {
                MessageBox.Show("完了しました。");
            }
        }
        bool cancel = false;
        private void button2_Click(object sender, EventArgs e) {
            cancel = true;
        }

        private void HeavyMethod() {
            while (!cancel) {
                System.Threading.Thread.Sleep(1000);
            }
        }

これを実行してStartを押すと「完了しました。」ダイアログがすぐに表示されます。


ここで登場するのがawait/asyncになります。
以下のように書く事で、HeavyMethodを待つようになり、Cancelボタンを押した後で「キャンセルしました。」とダイアログが表示されるようになります。
Taskで呼んだ別スレッド処理をawaitで待つ。(asyncはお約束)みたいな感じでしょうか。

        private async void button1_Click(object sender, EventArgs e) {
            await Task.Run(()=>HeavyMethod());
            if (cancel) {
                MessageBox.Show("キャンセルしました。");
            } else {
                MessageBox.Show("完了しました。");
            }
        }


また、Taskの例外処理は通常のコードの例外処理と同じ感じで書くことができます。

    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private async void button1_Click(object sender, EventArgs e) {
            try {
                await Task.Run(() => HeavyMethod());
                if (cancel) {
                    MessageBox.Show("キャンセルしました。");
                    cancel = false;
                } else {
                    MessageBox.Show("完了しました。");
                }
            } catch (Exception ex) {
                MessageBox.Show(ex.Message);
            }
        }
        bool cancel = false;
        private void button2_Click(object sender, EventArgs e) {
            cancel = true;
        }

        private void HeavyMethod() {
            int i = 0;
            while (!cancel && i++ < 5) {
                System.Threading.Thread.Sleep(1000);
                throw new ApplicationException("エラーが発生しました。");
            }
        }
    }


また、キャンセルに関しても用意されているものがあるので、それを使用した方が良いでしょう。

    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private async void button1_Click(object sender, EventArgs e) {
            try {
                ctokens = new CancellationTokenSource();
                await Task.Run(() => HeavyMethod(ctokens));
                MessageBox.Show("完了しました。");
            } catch (OperationCanceledException oce) {
                MessageBox.Show("キャンセルしました。");
            } catch (Exception ex) {
                MessageBox.Show(ex.Message);
            }
            ctokens = null;
        }
        CancellationTokenSource ctokens = null;
        private void button2_Click(object sender, EventArgs e) {
            if (ctokens != null) ctokens.Cancel();
        }

        private void HeavyMethod(CancellationTokenSource ctoken) {
            int i = 0;
            while (i++ < 5) {
                System.Threading.Thread.Sleep(1000);
                ctoken.Token.ThrowIfCancellationRequested();
            }
        }
    }

今度重い処理がある場合、積極的にasync/await Taskを使っていきたいと思います。