【c#】非同期処理のサンプルをケース別で用意してみた

C#

メインの処理とは別で非同期で動かしたいメソッド(処理)をパターン分けし、各パターンの非同期処理のサンプルを用意しました。

サンプルの動作確認環境

  • Windows10(x64)
  • VisualStudio2022
  • コンソールアプリ(.NETFramework4.8)
  • C#7.3

パターン分け

非同期で動かしたいメソッドを以下に着目して分類しました。

分類1:戻り値

  • void
  • string
  • Task
  • Task<string>
  • (string,double)

分類2:引数

  • 引数がない
  • 引数がある

分類3:await

  • 処理の中でawaitをつけて呼び出す処理はない
  • 処理の中でawaitをつけて呼び出す処理がある

(補足)async / awaitについて

  • メソッドにasyncキーワードをつけるだけでそのメソッドは非同期で動くが、メソッド内にawaitキーを使って起動するメソッドがなければならない(なければ警告がでる)
  • awaitキーワードは全てのメソッドに付けられるわけではなく、戻り値にTask等のメソッドに使用できる
  • awaitキーワードつければメソッドの終了を待つ(同期的に処理する)
  • awaitキーワードつければメソッドの戻り値を取得することができる

確認内容と結果に対する見解

以下のケースで非同期で動かすメソッド(処理)を作り、見解などをまとめました。

※Case6、Case8、Case13、Case15の戻り値を複数にしたバージョンは割愛。Case6、Case8、Case13、Case15時点でコンパイルエラーとなったため、同様にコンパイルエラーになる可能性があると判断。
  • 見解1:Task型(string等の戻り値無し)で非同期処理の進捗を管理したいのであれば、Case1~Case4で良いと思われる。Case9~Case12でも可能だがソースコードがごちゃごちゃすると思う(詳細はCase9~Case12項へ記載)
  • 見解2:Case6、Case8、Case13、Case15でコンパイルエラーが発生したが、Case6→Case14、Case8→Caee16、Case13→Case5、Case15→Case7でそれぞれ代替可能だと思われる
写真1:Case6とCase8で上記のようなコンパイルエラーが発生
写真2:Case13とCase8で上記のようなコンパイルエラーが発生

サンプルソース

記事の冒頭でも記載したサンプルの動作確認環境を再記しておきます。

サンプルの動作確認環境

  • Windows10(x64)
  • VisualStudio2022
  • コンソールアプリ(.NETFramework4.8)
  • C#7.3

Case1 戻り値:void、引数:無し、await:無し

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Case1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Task t = new Task(act);
            t.Start();
            Console.WriteLine("Main Case1");
            Console.Read();
        }

        static void act()
        {
            Thread.Sleep(5000);
            Console.WriteLine("act Case1");
        }
    }
}

Case2 戻り値:void、引数:無し、await:有り

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Case2
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Task t = new Task(act);
            t.Start();
            Console.WriteLine("Main Case2");
            Console.Read();
        }

        static async void act()
        {
            await Task.Delay(5000);
            Console.WriteLine("act Case2");

        }
    }
}

Case3 戻り値:void、引数:有り、await:無し

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Case3
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //objectの配列にすれば引数を複数指定することができる
            object[] paras = { "Case3", 3.3 };
            Task t = Task.Factory.StartNew(act, (object)paras);
            Console.WriteLine("Main Case3");
            Console.Read();
        }

        static void act(object para)
        {
            Thread.Sleep(5000);
            //少し強引な気もするが、受け取った引数をobjectの配列型に変換して要素を指定すれば引数を参照できる
            Console.WriteLine("act para1 is " + ((object[])para)[0].ToString() + " para2 is " + (double)((object[])para)[1]);
        }
    }
}

Case4 戻り値:void、引数:有り、await:有り

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Case4
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //objectの配列にすれば引数を複数指定することができる
            object[] paras = { "Case4", 4.4 };
            Task t = Task.Factory.StartNew(act, (object)paras);
            Console.WriteLine("Main Case4");
            Console.Read();
        }

        static async void act(object para)
        {
            await Task.Delay(5000);
            //少し強引な気もするが、受け取った引数をobjectの配列型に変換して要素を指定すれば引数を参照できる
            Console.WriteLine("act para1 is " + ((object[])para)[0].ToString() + " para2 is " + (double)((object[])para)[1]);
        }
    }
}

Case5 戻り値:string、引数:無し、await:無し

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Case5
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Task<string> t = new Task<string>(act);
            t.Start();
            Console.WriteLine("Main Case5");
            string r = t.Result;
            Console.WriteLine(r);
            Console.Read();
        }

        static string act()
        {
            Thread.Sleep(5000);
            return "act Case5";
        }
    }
}

Case7 戻り値:string、引数:有り、await:無し

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace Case7
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //objectの配列にすれば引数を複数指定することができる
            object[] paras = { "act Case7", 7.77 };
            Task<string> t = Task.Factory.StartNew(act, (object)paras);
            Console.WriteLine("Main Case7");
            string r = t.Result;
            Console.WriteLine(r);
            Console.Read();
        }

        static string act(object para)
        {
            Thread.Sleep(5000);
            //少し強引な気もするが、受け取った引数をobjectの配列型に変換して要素を指定すれば引数を参照できる
            return "act para1 is " + ((object[])para)[0].ToString() + " para2 is " + (double)((object[])para)[1];
        }
    }
}

Case14 戻り値:Task<string>、引数:無し、await:有り

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Case14
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //戻り値がTask<string>のメソッドの場合は、メソッドを呼び出すだけでタスクを起動する
            Task<string> t = act();
            Console.WriteLine("Main Case14");
            string r = t.Result;
            Console.WriteLine(r);
            Console.Read();
        }

        static async Task<string> act()
        {
            await Task.Delay(5000);
            return "act Case14";
        }
    }
}

Case16 戻り値:Task<string>、引数:有り、await:有り

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace Case16
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //戻り値がTask<string>のメソッドの場合は、メソッドを呼び出すだけでタスクを起動する
            Task<string> t = act("Case16", 16.16);
            Console.WriteLine("Main Case16");
            string r = t.Result;
            Console.WriteLine(r);
            Console.Read();
        }

        static async Task<string> act(string strPara, double dPara)
        {
            await Task.Delay(5000);
            return "act para1 is " + strPara + " para2 is " + dPara.ToString();
        }
    }
}

Case17 戻り値:(string,double)、引数:無し、await:無し

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace Case17
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Task<(string, double)> t = new Task<(string, double)>(act);
            t.Start();
            Console.WriteLine("Main Case17");
            string r1 = t.Result.Item1;
            double r2 = t.Result.Item2;
            Console.WriteLine("r1 = " + r1);
            Console.WriteLine("r2 = " + r2.ToString());
            Console.Read();
        }

        static (string, double) act()
        {
            Thread.Sleep(5000);
            return ("act Case17", 17.17);
        }
    }
}

Case18 戻り値:(string,double)、引数:有り、await:無し

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace Case18
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //objectの配列にすれば引数を複数指定することができる
            object[] paras = { "act Case18", 18.18 };
            Task<(string, double)> t = Task.Factory.StartNew(act, (object)paras);
            Console.WriteLine("Main Case18");

            string r1 = t.Result.Item1;
            double r2 = t.Result.Item2;
            Console.WriteLine("r1 = " + r1);
            Console.WriteLine("r2 = " + r2.ToString());
            Console.Read();
        }

        static (string, double) act(object para)
        {
            Thread.Sleep(5000);
            //少し強引な気もするが、受け取った引数をobjectの配列型に変換して要素を指定すれば引数を参照できる
            string r1 = ((object[])para)[0].ToString() + " act";
            double r2 = (double)((object[])para)[1] + 0.1;
            return (r1, r2);
        }
    }
}

Case19 戻り値:Task<(string, double)>、引数:無し、await:有り

case14の戻り値を複数にしたバージョン。Case14では何もせずそのまま実行できたがTask.Runで指定しないと実行できなくなった。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Case19
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Task<(string, double)> t = Task.Run(act);
            Console.WriteLine("Main Case19");
            string r1 = t.Result.Item1;
            double r2 = t.Result.Item2;
            Console.WriteLine("r1 = " + r1);
            Console.WriteLine("r2 = " + r2.ToString());
            Console.Read();
        }

        static async Task<(string, double)> act()
        {
            await Task.Delay(5000);
            return ("act Case19", 19.19);
        }
    }
}

Case20 戻り値:Task<(string, double)>、引数:有り、await:有り

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Case20
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Task<(string, double)> t = act("act Case20", 20.2);
            Console.WriteLine("Main Case20");
            string r1 = t.Result.Item1;
            double r2 = t.Result.Item2;
            Console.WriteLine("r1 = " + r1);
            Console.WriteLine("r2 = " + r2.ToString());
            Console.Read();
        }

        static async Task<(string, double)> act(string strPara, double dPara)
        {
            await Task.Delay(5000);
            string r1 = strPara + " r1";
            double r2 = dPara + 0.1;
            return (r1, r2);
        }
    }
}

Case9~Case12

Case9~Case12はソースコードが煩雑になる気がします。Case9~Case12はそれぞれCase1~Case4で代替可能かと思われますので、基本的にはCase1~Case4を使えばいいと思います。

Case9 戻り値:Task、引数:無し、await:無し

Task.Runを戻り値に指定して呼び出すときにTask.Runを使う、のような二重でTaskを起動するようなソースコードになる。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace Case9
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Task t = Task.Run(act);
            Console.WriteLine("Main Case9");
            Console.Read();
        }

        static Task act()
        {
            return Task.Run(() =>
            {
                Thread.Sleep(5000);
                Console.WriteLine("act Case9");
            });
        }
    }
}

Case10 戻り値:Task、引数:無し、await:有り

戻り値はTask型だがメソッドを呼び出すときにTask.Runの引数にメソッドを指定しなければならない。Task.Runを使わなくても起動してくれた方がソースが読みやすい気がする。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Case10
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Task t = Task.Run(act);
            Console.WriteLine("Main Case10");
            Console.Read();
        }

        static async Task act()
        {
            await Task.Delay(5000);
            Console.WriteLine("act Case10");
        }
    }
}

Case11 戻り値:Task、引数:有り、await:無し

Task.Runを戻り値に指定し呼び出すときにTask.Factory.startNewを使う、のように二重でTaskを起動するようなソースコードになる。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace Task_Pattern_Case11
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //objectの配列にすれば引数を複数指定することができる
            object[] paras = { "Case11", 11.11 };
            Task t = Task.Factory.StartNew(act, (object)paras);
            Console.WriteLine("Main Case11");
            Console.Read();
        }

        static Task act(object para)
        {
            return Task.Run(() =>
            {
                Thread.Sleep(5000);
                //少し強引な気もするが、受け取った引数をobjectの配列型に変換して要素を指定すれば引数を参照できる
                Console.WriteLine("act para1 is " + ((object[])para)[0].ToString() + " para2 is " + (double)((object[])para)[1]);
            });
        }
    }
}

Case12 戻り値:Task、引数:有り、await:有り

戻り値はTask型だがメソッドを呼び出すときにTask.Factory.StartNewの引数にメソッドを指定しなければならない。Task.Factory.StartNewを使わなくても起動してくれた方がソースを読みやすい気がする。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Case12
{
    internal class Program
    {
        static void Main(string[] args)
        {
            //objectの配列にすれば引数を複数指定することができる
            object[] paras = { "Case12", 12.12 };
            Task t = Task.Factory.StartNew(act, (object)paras);
            Console.WriteLine("Main Case12");
            Console.Read();
        }

        static async Task act(object para)
        {
            await Task.Delay(5000);
            //少し強引な気もするが、受け取った引数をobjectの配列型に変換して要素を指定すれば引数を参照できる
            Console.WriteLine("act para1 is " + ((object[])para)[0].ToString() + " para2 is " + (double)((object[])para)[1]);
        }
    }
}

タイトルとURLをコピーしました