LoginSignup
1
4

More than 3 years have passed since last update.

C# async / await / Taskの使い方(備忘録)

Posted at

きっかけ

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

1
4
0

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
1
4