◆ 割と似てる
◆ JavaScript ⇨ Promise
◆ C# ⇨ Task

async 中に throw すると await のところでエラー

両方とも async 関数を実行してその中でエラーがあっても await するまで例外は throw されません
await したところでエラーになります

async function fn() {
try {
const value = async()
console.log(1)
await value
console.log(2)
} catch (err) {
console.log(err)
}
}

async function async() {
throw new Error("err")
}

fn()
1
Error: err

private async void fn()
{
try
{
var value = async();
Console.WriteLine(1);
await value;
Console.WriteLine(2);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}

private async Task<int> async()
{
throw new Exception("err");
}

fn();
1
System.Exception: err

1 は表示されますが 2 は表示されません

async 関数だけだと非同期処理にならない

async 関数を実行してもその中が非同期で実行されるわけではありません

async function fn() {
const value = async()
console.log(2)
}

async function async() {
console.log(1)
return 100
}

fn()
1
2

private async void fn()
{
var value = async();
Console.WriteLine(2);
}

private async Task<int> async()
{
Console.WriteLine(1);
return 100;
}

fn();
1
2

先に 1 が表示されます
C# で 1 の前に適当に Sleep を入れても一緒です

ところで Task<int> が返り値ですが async 関数の場合は int を reutrn させます
JavaScript でも asycn 関数は return 値が Promise に包まれるので同じようなものです

非同期処理にする

async function fn() {
const value = async()
console.log(2)
}

async function async() {
return Promise.resolve().then(() =>
{
console.log(1)
return 100
})
}

fn()
2
1

JavaScript はシングルスレッドなので Promise にしても同じスレッドで実行されます
「new Promise(() => {})」 形式だと即時実行されるので 中で await でもしない限り 1 が先に来ます
then にすると非同期で後回しになるので 2 が先に来ます

private async void fn()
{
var value = async();
Console.WriteLine(2);
}

private async Task<int> async()
{
return await Task.Run<int>(() =>
{
Console.WriteLine(1);
return 100;
});
}

fn();
2
1

C# の Task では渡した関数が別スレッドで実行されます

メソッドに async があると Task<int> が返り値なのに Task<int> を返せず int しか返せません
int 型を取り出すために await が必要です
今回の場合は 中で await を使う必要ないので async をはずしてしまってもいいです

private Task<int> async()
{
return Task.Run<int>(() =>
{
Console.WriteLine(1);
return 100;
});
}

ただ JavaScript と合わせるために今回は一応書いてます
C# だと返り値を書くので 定義の 1 行目だけで Task という単語があって非同期処理を扱うとわかります
JavaScript だと中を見ないとわからないので個人的に Promise を返すのなら不要でもわかりやすく async を書くようにしています

await 以外で非同期のあとに続ける

await ではないメソッドチェーンの方法で Promise/Task の処理の続きを書きます

async function fn() {
async().then(val => {
console.log(val)
})
}

async function async() {
return Promise.resolve().then(() =>
{
return 100
})
}

fn()
100

private async void fn()
{
async().ContinueWith(async val =>
{
Console.WriteLine(val);
Console.WriteLine(await val);
});
}

private async Task<int> async()
{
return await Task.Run<int>(() =>
{
return 100;
});
}

fn();
System.Threading.Tasks.Task`1[System.Int32]
100

JavaScript では then で受け取ったものに結果が入っていましたが C# では受け取ったものも Task になっています
なので await が必要です
await するには async メソッドでないといけないので ContinueWith の引数は基本 async になると思います

エラーのあとに続ける

非同期処理中にエラーが起きた場合です

async function fn() {
async().then(val => {
console.log(val)
}).catch(err => {
console.log("error>>>", err)
})
}

async function async() {
return Promise.resolve().then(() =>
{
throw new Error("err")
})
}

fn()
error>>> Error: err

then は実行されず 代わりに catch が実行されます

private async void fn()
{
async().ContinueWith(async val =>
{
try
{
Console.WriteLine(await val);
}
catch (Exception ex)
{
Console.WriteLine("err>>>");
Console.WriteLine(ex);
}
});
}

private async Task<int> async()
{
return await Task.Run<int>(() =>
{
throw new Exception("err");
return 100;
});
}

fn();
err>>>
System.Exception: err

return 100; をあえて書いていますが型推論させるためです

Task ではエラーが起きたかを問わず ContinueWith で続けられます
受け取った Task のステータスがエラーになっているので await すると例外が起きます

特別理由がないなら ContinueWith で受け取ったものがエラーかどうかの try-catch は ContinueWith の中でするのが良いと思います
外で await する場合は 2 重 Task になっていて await も 2 回必要です

private async void fn()
{
try
{
await await async().ContinueWith(async val =>
{
await val;
});
}
catch (Exception err)
{
Console.WriteLine(err);
}
}

private async Task<int> async()
{
return await Task.Run<int>(() =>
{
throw new Exception("err");
return 100;
});
}

fn();
System.Exception: err

JavaScript の場合は多重 Promise は自動で 1 重になるのでこういうところは楽です

async function fn(){
console.log(await Promise.resolve(Promise.resolve(Promise.resolve(100))))
}

fn()
100

Task のプロパティ

Promise と違って Task は値やステータスをプロパティから参照可能です
エラーが起きた場合の例外も保持しています

[通常の場合]
> var task = Task.Run(() => { return 1; });
. task.Result
1
> task.Exception
null
> task.Status
RanToCompletion

[エラーの場合]
> var task = Task.Run(() => { throw new Exception("err"); return 1; });
> task.Result
1 つ以上のエラーが発生しました。
+ System.Threading.Tasks.Task.ThrowIfExceptional(bool)
+ Task<TResult>.GetResultCore(bool)
+ Task<TResult>.get_Result()

> task.Exception
AggregateException(Count = 1)
> task.Exception.InnerExceptions[0]
[System.Exception: err
場所 Submission#62.<>c.<<Initialize>>b__0_0()
場所 System.Threading.Tasks.Task`1.InnerInvoke()
場所 System.Threading.Tasks.Task.Execute()]

> task.Status
Faulted