LoginSignup
42
60

async/await 比較(C#, JavaScript, Python)

Last updated at Posted at 2024-01-02

メッセージループ

C#

Dispatcher.Run()

using System.Windows.Threading;

Dispatcher.CurrentDispatcher.InvokeAsync(async () =>
{
   // いろいろな処理...

   // メッセージループを終了させる
   Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Normal);
});

Dispatcher.Run();

WPF とか WinForms とかでは、フレームワークがすでにメッセージループを回しているので、自分で Dispatcher.Run() する必要はない。

JavaScript

自分で書く必要なし。

Python

asyncio.run(最初に実行するasync関数)

import asyncio

async def main():
    # いろいろな処理...
    pass

asyncio.run(main())

await で待つ sleep

実用的に使うことは少ないけど、実験ではよく使用するので...

C#

await Task.Delay(1500); // 単位はミリ秒

JavaScript

ない... ので、自分で作る。

function sleepAsync(t) {
    return new Promise(resolve => setTimeout(resolve, t));
}
// 使い方
await sleepAsync(1500); // 単位はミリ秒

Python

import asyncio
await asyncio.sleep(1.5) # 単位は秒

普通の sleep

通常、使わない。けど、実験ではよく使うので...

C#

System.Threading.Thread.Sleep(1500); // 単位はミリ秒

JavaScript

ない。ので、無理やり作る。
※ 他の言語での組み込み sleep と違って、これは、CPUリソースを消費する

function sleep(t) {
  const startTime = (new Date()).getTime();
  while ((new Date()).getTime() - startTime < t) {}
}
// 使い方
sleep(1500); // 単位はミリ秒

Python

import time
time.sleep(1.5) # 単位は秒

async な関数(中で await が使用可能な関数)の定義

C#

戻り値がある場合は、「async Task<戻り値の型> FooAsync() { ... }」
戻り値がない場合は、「async Task FooAsync() { ... }」
※ 「async void FooAsync() {...}」でもOKだけど、await できない関数になる

ラムダ式は「async () => ...」

async Task<int> FooAsync()
{
    int ret1 = await FooAsync1();
    int ret2 = await FooAsync2();
    return ret1 + ret2;
}
// 使うときはこんな感じ
int val = await FooAsync();

JavaScript

「async function fooAsync() { ... }」
ラムダ式は「async () => ...」

async function fooAsync() {
    const ret1 = await fooAsync1();
    const ret2 = await fooAsync2();
    return ret1 + ret2;
}
// 使うときはこんな感じ
let val = await fooAsync();

Python

「async def fooAsync(): ...」
ラムダ式で async は、Python にはない。

async def fooAsync():
    ret1 = await fooAsync1()
    ret2 = await fooAsync2()
    return ret1 + ret2

# 使うときはこんな感じ
val = await fooAsync()

async な関数の実行

C#

実行を開始して終わるまで待つ場合は、「await FooAsync()」

終わるのを待たずに実行する場合は、普通に関数を実行する。
後で、終わるまで待ちたい場合は、戻り値を取っておいて、後でそれを await する。

var task1 = FooAsync1(); // FooAsync1 を実行
var task2 = FooAsync2(); // FooAsync1 の実行が終わる前に FooAsync2 が実行が開始される
await task1; // FooAsync1 の実行が終わるまで待つ
await task2; // FooAsync2 の実行が終わるまで待つ

JavaScript

C# と同じ。

const task1 = fooAsync1(); // fooAsync1 を実行
const task2 = fooAsync2(); // fooAsync1 の実行が終わる前に fooAsync2 が実行が開始される
await task1; // fooAsync1 の実行が終わるまで待つ
await task2; // fooAsync2 の実行が終わるまで待つ

Python

実行を開始して終わるまで待つ場合は、他と同様「await fooAsync()」

終わるのを待たずに実行する場合は、「asyncio.create_task(fooAsync())」

task1 = asyncio.create_task(fooAsync1()) # fooAsync1 を実行
task2 = asyncio.create_task(fooAsync2()) # fooAsync1 の実行が終わる前に fooAsync2 が実行が開始される
await task1 # fooAsync1 の実行が終わるまで待つ
await task2 # fooAsync2 の実行が終わるまで待つ

Python の場合、async な関数は呼び出しただけでは、実行が開始されない。
await するとか、asyncio.create_task() とかした時点で実行が開始される。
(なので、上記の例を C#, JavaScript と同じように書くと、fooAsync1, fooAsync2 は、並列に実行されず、順番に実行される)

実行されることなく、戻り値のオブジェクトが解放されると
「RuntimeWarning: coroutine 'xxx' was never awaited」
のようなメッセージが出る。

コールバックを async, await で扱えるようにする

C#

System.Threading.Tasks.TaskCompletionSource を利用。

Task<Info> GetCallbackedInfoAsync(SomeCalculator calculator)
{
    var tcs = new TaskCompletionSource<Info>();
    // Calc メソッドは、何か計算して結果をコールバックで通知する
    calculator.Calc(info => tcs.SetResult(info));
    return tcs.Task;
}

// 使うときはこんな感じ
var calculator = new SomeCalculator();
Info info = await GetCallbackedInfoAsync(calculator);

JavaScript

Promise を利用。

function getCallbackedInfoAsync(calculator) {
    return new Promise(resolve => {
        calculator.calc(info => resolve(info));
    });
}
// 使うときはこんな感じ
const calculator = new SomeCalculator();
const info = await getCallbackedInfoAsync(calculator);

Python

asyncio.Future を利用。

import asyncio

def getCallbackedInfoAsync(calculator: SomeCalculator):
    future = asyncio.Future()
    def callbackFunc(info):
        future.set_result(info)
    # calc メソッドは、何か計算して結果をコールバックで通知する
    calculator.calc(callbackFunc)
    return future

# callback が別スレッドで呼び出される場合...
def getCallbackedInfoAsync2(calculator: SomeCalculator):
    msgLoop = asyncio.get_running_loop()
    future = asyncio.Future()
    def callbackFunc(info):
        # asyncio.Future はスレッドセーフじゃないので、呼び出し元のスレッド上で結果をセットする
        msgLoop.call_soon_threadsafe(future.set_result, info)
    # calc メソッドは、何か計算して結果をコールバックで通知する
    calculator.calc(callbackFunc)
    return future


# 使うときはこんな感じ
calculator = SomeCalculator()
info = await getCallbackedInfoAsync(calculator)

generator

C# (ver. 8 以降)

// generator の作成
async IAsyncEnumerable<int> CountAsync(int n)
{
    for (int i = 0; i < n; i++)
    {
        await Task.Delay(500);
        yield return i;
    }
}
// 使うときはこんな感じ
async Task TestAsync()
{
    await foreach (var n in Count(5))
    {
        Console.WriteLine(n);
    }
});

JavaScript

function sleepAsync(t) { return new Promise(resolve => setTimeout(resolve, t)); }

// generator の作成
async function* countAsync(n) {
    for (let i = 0; i < n; i++) {
        await sleepAsync(500);
        yield i;
    }
}
// 使うときはこんな感じ
async function testAsync() {
    for await (const n of countAsync(5)) {
        console.log(n);
    }
}

Python

# generator の作成
async def countAsync(n):
    for i in range(n):
        await asyncio.sleep(0.5)
        yield i

# 使うときはこんな感じ
async def testAsync():
    async for i in countAsync(5):
        print(i)

別スレッドで実行して、await で終わるまで待つ

C#

await Task.Run(() =>
{
    // この部分が別スレッドで実行される
});
// 別スレッドでの実行が終わると、ここに到達する

Python

※ Python 3.9 以降

def someFunc():
    # この部分が別スレッドで実行される
    ...

await asyncio.to_thread(someFunc)

ワーカースレッド(メッセージループを持つスレッド)を作る

C#

// ワーカースレッドを作成して、その Dispatcher を返す
System.Windows.Threading.Dispatcher>CreateWorkerThread()
{
    var tcs = new TaskCompletionSource<System.Windows.Threading.Dispatcher>();
    var thread = new Thread(() =>
    {
        tcs.SetResult(System.Windows.Threading.Dispatcher.CurrentDispatcher);
        System.Windows.Threading.Dispatcher.Run();
    });
    thread.Start();
    return tcs.Task.Result;
}
// 使う時はこんな感じ...
var workerDispatcher = CreateWorkerThread();
// ワーカースレッド上で実行させる
await workerDispatcher.InvokeAsync(() =>
{
    // この部分がワーカースレッド上で実行される
});
// ワーカースレッドを終了させる
workerDispatcher.BeginInvokeShutdown(System.Windows.Threading.DispatcherPriority.Normal);

JavaScript

worker.js
self.addEventListener('message', e => {
    // main.js で postMessage されたときに、この部分がワーカースレッド上で実行される
    const calcResult = e.data * e.data;
    self.postMessage(calcResult);
});
main.js
const worker = new Worker('worker.js');
worker.addEventListener("message", e => {
    console.log(e.data);
});
worker.postMessage(3);

Python

import asyncio
import threading
import concurrent.futures
import time

# ワーカースレッドを作成して、そのメッセージループを返す
def createWorkerThread() -> asyncio.AbstractEventLoop:
    future = concurrent.futures.Future()
    def workerThreadMain():
        msgLoop = asyncio.new_event_loop()
        asyncio.set_event_loop(msgLoop)
        future.set_result(msgLoop)
        msgLoop.run_forever()

    thread = threading.Thread(target=workerThreadMain)
    thread.start()
    return future.result()

# 使うときは、こんな感じ...

async def main():
    mainMsgLoop = asyncio.get_running_loop()
    workerMsgLoop = createWorkerThread()
    for i in range(5):
        # ワーカースレッド上で testFunc を実行させる
        f = asyncio.Future()  # 結果を待つための Future
        workerMsgLoop.call_soon_threadsafe(testFunc, mainMsgLoop, f, i)
        val = await f # 結果を取得
        print(f"result={val}")
    # ワーカースレッドを停止
    workerMsgLoop.call_soon_threadsafe(lambda: workerMsgLoop.stop())

# 1秒かけて、与えられた数の2乗を計算
def testFunc(msgLoop: asyncio.AbstractEventLoop, future : asyncio.Future, i):
    time.sleep(1)
    calcResult = i * i
    msgLoop.call_soon_threadsafe(lambda: future.set_result(calcResult))

asyncio.run(main())
print("main done")

インタラクティブ環境

C#

  • VisualStudio 2022 の C#インタラクティブ
    • await は、利用可能
    • async 関数を await なしで実行すると、ちゃんとバックグラウンドで実行される
  • その他、コンソール上のインタラクティブ環境
    • メッセージループが実行されていない
      (await は、動作するが、マルチスレッドになる)

JavaScript

  • ブラウザのデバッグコンソール
    • await は、利用可能
    • async 関数を await なしで実行すると、ちゃんとバックグラウンドで実行される

Python

  • ipython
    • await は、利用可能
    • asyncio.create_task(...) は、エラーになる
  • Jupyer Notebook
    • await は、利用可能
    • asyncio.create_task(...) も OK (ちゃんとバックグラウンドで実行される)
    • バックグラウンドで実行されたものの標準出力は、直近に実行した出力結果のところに表示される

その他...

C# で、メッセージループがないとき

Python では、メッセージループが起動していないときは、async, await を使用するとエラーになる。
JavaScript は、そもそも、メッセージループが起動していない、という状況は存在しない。
でも、C# ではエラーにならず、動作する。

ただ... メッセージループの有無で結構挙動が異なる。
以下のようなプログラムで違いを見てみる。

async Task Test()
{
    Console.WriteLine($"Start TID={Thread.CurrentThread.ManagedThreadId}");
    var task1 = Foo("A");
    var task2 = Foo("B");
    await task1;
    Console.WriteLine($"task1 done TID={Thread.CurrentThread.ManagedThreadId}");
    await task2;
    Console.WriteLine($"task2 done TID={Thread.CurrentThread.ManagedThreadId}");
}
async Task Foo(string tag)
{
    Console.WriteLine($"{tag}: TID={Thread.CurrentThread.ManagedThreadId}");
    for (int i = 0; i < 3; i++)
    {
        await Task.Delay(100);
        Console.WriteLine($"{tag}: Before sleep TID={Thread.CurrentThread.ManagedThreadId}");
        Thread.Sleep(200);
        Console.WriteLine($"{tag}: After  sleep TID={Thread.CurrentThread.ManagedThreadId}");
    }
}

メッセージループがある状態とない状態で、それぞれ Test() を実行した場合、以下のようになる。

メッセージループあり メッセージループなし
Start TID=1
A: TID=1
B: TID=1
B: Before sleep TID=1
B: After sleep TID=1
A: Before sleep TID=1
A: After sleep TID=1
B: Before sleep TID=1
B: After sleep TID=1
A: Before sleep TID=1
A: After sleep TID=1
B: Before sleep TID=1
B: After sleep TID=1
A: Before sleep TID=1
A: After sleep TID=1
task1 done TID=1
task2 done TID=1
Start TID=1
A: TID=1
B: TID=1
B: Before sleep TID=4
A: Before sleep TID=5
B: After sleep TID=4
A: After sleep TID=5
A: Before sleep TID=4
B: Before sleep TID=6
B: After sleep TID=6
A: After sleep TID=4
A: Before sleep TID=5
B: Before sleep TID=6
A: After sleep TID=5
B: After sleep TID=6
task1 done TID=5
task2 done TID=5

メッセージループがない場合、A と B が異なるスレッドで動作するため、Thred.Sleep(200) が同時に動いてしまう。

Python の async with

    async with foo() as obj:
        いろいろな処理

これは、以下とほぼ同等
(例外発生時に __aexit__ に例外情報が与えられる)

    obj = foo()
    await obj.__aenter__()
    try:
        いろいろな処理
    finally:
        await obj.__aexit__(None, None, None)
42
60
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
42
60