メッセージループ
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
self.addEventListener('message', e => {
// main.js で postMessage されたときに、この部分がワーカースレッド上で実行される
const calcResult = e.data * e.data;
self.postMessage(calcResult);
});
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)