.net column

.NET開発者のためのブログメディア
数字と文字

C#の非同期プログラミングを基本から解説!

2020年08月20日
SE
非同期プログラミングという言葉の意味を教えてください。
PM
まずは非同期とは何か、基本から見ていきましょう。

非同期とは?

C#の非同期プログラミングについて解説する前に、まず非同期とは何なのかについて簡単に説明します。非同期の処理とは「同期ではない処理」という意味で、同期処理とはプログラミングした順番に処理が進むことです。普通にプログラムすれば同期処理になります。

それに対して非同期処理とは、「複数のプログラムを並列で処理すること」です。A→B→Cの順で処理を行う場合、同期の場合はAの処理が全て終わった後にBを開始し、Bの処理が全て終わった後にCを開始します。

ところが非同期の場合は、AとBとCを平行で同時に処理します。その非同期処理をC#で実現する方法をこれから説明しましょう。

非同期処理のC#サンプル

それではC#の非同期プログラムのサンプルを実行しましょう。以下が簡単な例です。ソースの先頭には「using System.Threading.Tasks;」と記述してください。

static void Main(string[] args)
{
Task t = Task.Run(() => { AsyncWork(); });

for (int i = 0; i < 100; i++)
{
Console.WriteLine(“”処理A実行””+i);
}
Task.WaitAll(t);
}

static void AsyncWork()
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine(“”処理B実行””+i);
}
}

Taskクラスで非同期処理を実行できる

上のC#サンプルを実行すると、「処理A実行」と「処理B実行」が交互でなくバラバラに表示されることが確認できます。非同期処理は「Task t = Task.Run(() => { AsyncWork(); });」で実現しています。

TaskクラスのRunメソッドでMainメソッドと平行に処理をしたいAsyncWorkを指定することで、非同期で処理されるのです。そして、最後のTask.WaitAllでAsyncWorkの終了を待っています。

このような非同期処理は、UIを持つアプリケーションで利用されます。ブラウザでは大きなファイルをダウンロードしている間もWebを閲覧できますが、もし同期処理の場合はダウンロードが終わるまでブラウザを全く使えなくなります。

非同期処理の必要性はそこにあります。

戻り値を取得するC#サンプル

Taskクラスは戻り値を受け取ることができます。ここまでのC#サンプルを以下のように修正してみましょう。

static void Main(string[] args)
{
Task<string> t = Task.Run(() => { return AsyncWork(); });

for (int i = 0; i < 100; i++)
{
Console.WriteLine(“”処理A実行””+i);
}
Console.WriteLine(t.Result);
}

static string AsyncWork()
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine(“”処理B実行””+i);
}
return “”完了””;
}

例外をキャッチするC#サンプル

上のC#サンプルを実行すると、最後に「完了」と表示されます。AsyncWorkメソッドの戻り値の文字列を取得できていることがわかります。Task.WaitAllが無くても戻り値を待つ処理があるので、それでAsyncWorkの終わりを待つことが出来ます。

非同期処理中に発生した例外をキャッチしたい場合はどうすればよいのでしょうか。以下がそのC#サンプルです。

static void Main(string[] args)
{
Task t = Task.Run(() => { AsyncWork(); });

try
{
t.Wait();
} catch (Exception e)
{
Console.WriteLine(e.Message);
}
}

static void AsyncWork()
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine(“”処理B実行””+i);
if (i == 50) throw new Exception(“”テスト””);
}
}

Waitメソッドで例外発生を待つことが可能

上のC#サンプルを実行すると、最後は以下のように表示されます。

処理B実行50
One or more errors occurred. (テスト)

例外をキャッチできていることがわかりますね。例外を受けるにはこのようにtry~catchの中でWaitメソッドを使用して、待つ必要があります。

また、こういった非同期プログラミングをしていると、複数のタスクを待ってそれぞれの結果を得たいということがあると思います。次の項のサンプルを実行してみてください。

複数のタスクの終了を待つC#サンプル

static void Main(string[] args)
{
Task<int> t1 = Task.Run(() => { return AsyncWork1(); });
Task<int> t2 = Task.Run(() => { return AsyncWork2(); });

Task.WaitAll(t1, t2);

Console.WriteLine(t1.Result + t2.Result);
}

static int AsyncWork1()
{
int sum = 0;
for (int i = 0; i < 100; i++)
{
Console.WriteLine(“”処理A実行””+i);
sum++;
}
return sum;
}

static int AsyncWork2()
{
int sum = 0;
for (int i = 0; i < 50; i++)
{
Console.WriteLine(“”処理B実行””+i);
sum++;
}
return sum;
}

WaitAllメソッドで複数のタスクの処理を待てる

上のC#サンプルを実行すると、最後に150と表示されます。AsyncWork1とAsyncWork2の結果を足して表示しています。Task.WaitAllは複数のタスクがすべて終わるまで待つことができるのです。このように待つことを「同期をとる」とも言います。

なおC#の非同期処理は別の記述の仕方もあります。それはasync/awaitを使うやり方です。次の項にそのサンプルを載せます。なお、ソースの先頭に「using System.Threading;」を記述してください。

async/awaitのC#サンプル

static void Main(string[] args)
{
AsyncWork();
for (int i = 0; i < 100; i++)
{
Console.WriteLine(“”処理A実行””+i);
Thread.Sleep(10);
}
}

static async void AsyncWork()
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine(“”処理B実行””+i);
await Task.Delay(10);
}
}

async/awaitだけを使用した時の問題点

上のサンプルを実行すると、処理A実行の表示と処理B実行の表示が並列で行われます。

Mainメソッド内部ではThread.Sleepで他の非同期処理を待ち、asyncにしたAsyncWorkメソッドではawait Task.Delayで他の非同期処理を待っています。awaitはasyncにしたメソッドだけで使用できます。

ただし処理A実行は99の最後まで表示されますが、処理B実行は途中で終わってしまいます。MainメソッドがAsyncWorkが終わるのを待たずに終了してしまうのです。この問題を解決するのが、以下のC#サンプルです。

asyncメソッドの終了を待つC#サンプル

static void Main(string[] args)
{
Task t = AsyncWork();
for (int i = 0; i < 100; i++)
{
Console.WriteLine(“”処理A実行””+i);
Thread.Sleep(10);
}
Task.WaitAll(t);
}

static async Task AsyncWork()
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine(“”処理B実行””+i);
await Task.Delay(10);
}
}

Task.WaitAllでasyncメソッドの終了を待てる

上のサンプルを実行すると、処理B実行の表示も99まで実行されることが確認できます。asyncメソッドにTaskクラスを使用することで、WaitAllで終了を待つことができるのです。

しかし結局Taskクラスによる制御が必要になるのであれば、敢えてasync/awaitを使う意味は無いように思えます。それでもasyncを使ったメソッドは非同期であることがより明確になる、という意義はあるでしょう。

C#の非同期プログラミングは難しくない

非同期処理のプログラミングは難しいイメージがありますが、今回説明したようにC#では手軽に行うことができます。もしC#で時間のかかる処理を行う場合、非同期プログラミングで処理の進捗を表示するなどして活用しましょう。

SE
複数のプログラムの並列処理が非同期なんですね。
PM
手軽に行うことができますのでまず自分で書いてみて覚えることが重要ですね。

.NET分野でのキャリアアップをお考えの方は、現在募集中の求人情報をご覧ください。

求人一覧

また、直接のエントリーも受け付けております。

エントリー(応募フォーム)