きっかけ
https://qiita.com/takmot/items/83ca6350b10af959f3db
上記記事で書いた以下コード
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
using SignalRChat.Hubs;
namespace SignalRChat
{
public class app
{
private IHubContext<ChatHub> Hub
{
get;
set;
}
public app(IHubContext<ChatHub> hub)
{
Hub = hub; // ハブコンテキストを取得
}
public async Task pushMessage(string msg)
{
await Hub.Clients.All.SendAsync("pushMessage", msg); // メッセージ送信
}
}
}
pushMessage
コールの際にawaitを付けないと、この呼び出しを待たないため、現在のメソッドの実行は、呼び出しが完了するまで続行します。呼び出しの結果に 'await' 演算子を適用することを検討してください。
と警告が出ます。
このTaskの扱いに困ったため、色々記事は読んでみたものの、完全には理解できなかった。
とりあえず、現在の自分の理解と対処について記載しようと思います。
(補足..)
警告が出るのはasync Task
のメソッドコール時で、
SendAsyncは、awaitを付けずにコールしても警告は出ませんでした。
しかし、awaitを付けないとasync void
パターンと同様にタスクの終了が待てない、投げっぱなしになることなのでよろしくないようです。
async / await / Task
上記警告が出るため、Taskに対してawaitを付ける。
awaitを使用しているメソッドにはasyncを付ける必要があるため、asyncを付ける。(下記例method_A()
)
そのメソッドをコールしようとすると同様に警告が出る。
そのため、コール元もawait/asyncを付ける。
async void
を使えばawaitはいらなくなるが、async void
は使うなと言われている。
static async Task method_A()
{
await Task.Run(() => {
Console.WriteLine($"task contents : {Thread.CurrentThread.ManagedThreadId}");
});
Console.WriteLine("method_A Completed");
}
static async Task method_B()
{
await method_A();
Console.WriteLine("method_B Completed");
}
async void
を使わない理由としては以下になります。
- awaitで待ち受けできなくなり、そのためスレッドの終了を知ることが出来ない、投げっぱなし(fire and forget)になる。
- 例外を捕獲できない
そこで、以下のような実装にしました。
コメントは実行順番と、実行されるスレッドです。
ContinueWith
でタスク終了後にタスクの状態を見ています。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace task_sample
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"Main start : {Thread.CurrentThread.ManagedThreadId}"); // 1 (スレッドA)
AsyncMethod().ContinueWith((task) => {
Console.WriteLine($"task.status {task.Status} : {Thread.CurrentThread.ManagedThreadId}"); // 6 (スレッドC)
});
Console.WriteLine($"Main end : {Thread.CurrentThread.ManagedThreadId}"); // 4 (スレッドA)
Console.ReadLine();
}
static async Task AsyncMethod()
{
Console.WriteLine($"AsyncMethod start : {Thread.CurrentThread.ManagedThreadId}"); // 2 (スレッドA)
await Task.Run(() => {
Console.WriteLine($"task contents : {Thread.CurrentThread.ManagedThreadId}"); // 3 (スレッドB)
});
Console.WriteLine($"AsyncMethod end : {Thread.CurrentThread.ManagedThreadId}"); // 5 (スレッドB)
}
}
}
task.status
で以下のようなタスクのステータスが確認できます。
- TaskStatus.RanToCompletion : 正常終了した
- TaskStatus.Canceled : キャンセルされた
- TaskStatus.Faulted : 例外が発生した
Taskが戻り値を持っている場合
task.Resultで戻り値を取得できます。
using System;
using System.Threading;
using System.Threading.Tasks;
namespace task_sample
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"Main start : {Thread.CurrentThread.ManagedThreadId}"); // 1 (スレッドA)
AsyncMethod().ContinueWith((task) => {
Console.WriteLine($"task.status {task.Status} : {Thread.CurrentThread.ManagedThreadId}"); // 6 (スレッドC)
Console.WriteLine($"task.Result {task.Result} : {Thread.CurrentThread.ManagedThreadId}");
});
Console.WriteLine($"Main end : {Thread.CurrentThread.ManagedThreadId}"); // 4 (スレッドA)
Console.ReadLine();
}
static async Task<string> AsyncMethod()
{
Console.WriteLine($"AsyncMethod start : {Thread.CurrentThread.ManagedThreadId}"); // 2 (スレッドA)
await Task.Run(() => {
Console.WriteLine($"task contents : {Thread.CurrentThread.ManagedThreadId}"); // 3 (スレッドB)
});
Console.WriteLine($"AsyncMethod end : {Thread.CurrentThread.ManagedThreadId}"); // 5 (スレッドB)
return "Task Completed";
}
}
}
Task.Run()
Task.Run()
は、
何かの通信で受信待ち→受信処理を繰り返す
無限ループを使用するような場合だけ使用するイメージで理解しました。
以下記事でUDPパケット受信タスクを作りました。
https://qiita.com/takmot/items/6287911115575ab84b05
参考記事
https://qiita.com/acple@github/items/8f63aacb13de9954c5da
https://qiita.com/inew/items/0126270bca99883605de
https://qiita.com/hiki_neet_p/items/d6b3addda6c248e53ef0
https://qiita.com/4_mio_11/items/f9b19c04509328b1e5c1
https://qiita.com/rawr/items/5d49960a4e4d3823722f
http://neue.cc/2013/10/10_429.html
https://stackoverflow.com/questions/62444555/can-i-omit-await-of-clients-all-sendasync-in-net-core-signalr