あるところにC# / .NET Frameworkで作られている業務システムありけり。
ある日ユーザーから「メインメニューから子画面を開いた後、メインメニューがずっと残りっぱなしで移動もできないのは邪魔すぎる何を考えとるんか誰じゃこんなシステムを作ったやつは(以下略)」とお叱りを受けた。
※修正後、しばらくして今度は「何で子画面を開いたらメインメニューが消えるんだ何を考えとるんか誰じゃこんなシステムを作ったやつは(以下略)」とお叱りを受けたので元に戻した。何なんだ一体。
調べてみると子画面をモーダルウィンドウとして表示しており、ほんならモードレスに変えましょうと相成った。
この記事の続き。手こずっているシステムのバグ対応でまた躓いた。
Entity Frameworkなんて使ったことねぇよ。わかんねー
事の発端
とある画面を表示する際、初期表示データを取得するためのDBアクセス時にEntity Frameworkが例外エラーを吐くことがある。
A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
at NameSpace.DataAccess.CustomerMasterAccess.GetItem(String customerCode) in C:\*****.cs:line 74
at NameSpace.DataAccess.MasterAccess.GetCustomerName(String customerCode) in C:\*****.cs:line 125
at NameSpace.Forms.DataInput.SetCustomerName(String customerCode, String customerCode2, String customerCode3) in C:\*****.cs:line 475
at NameSpace.Forms.DataInput.DisplayItems() in C:\*****.cs:line 90
at NameSpace.Forms.DataInput.Initial() in C:\*****.cs:line 71
at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__140_0(Object state)
"A second operation started on this context before a previous operation completed"というエラーメッセージでググってみると、「DbContext はスレッドセーフではないので、スレッド間で使いまわしてはいけない」という文言が見つかる。
つまりこのエラーは複数スレッドから同じDbContextにアクセスしてしまったため発生しているようだ(エラーメッセージに書いている通りやね)
格安モバイル回線(MVNO)の一つであるmineoには「パケット放題 Plus」というオプションがあり、月額350円で1.5Mbpsの通信が使い放題になる。
1.5Mbpsあれば画像コンテンツはもちろんのこと、動画コンテンツについても480pならYoutube動画も見られるしビデオ通話もできる。基本的に困ることはない。
ただし通信量の制限がある
ただ一つだけ落とし穴があり「3日間で10GB以上利用すると速度制限がかかる」という制約がある。
まぁ本当に無制限となると無茶な使い方をする人が出てくるので仕方がない。
そんなに使うこともないし大丈夫だろう、と思っていた。
やたらawaitしてるコードに出会う
他人が作った業務システムの手直ししているとこんなコードに出会った。
string contents1 = await DoSomething1Async();
string contents2 = await DoSomething2Async();
string contents3 = await DoSomething3Async();
DoSomething*Async()を見るとasync
修飾子がついてて、それをawait
で受け止めてる形だ。
なんとなく非同期処理だなぁとは分かるが...恥ずかしながら「重い処理を実行するときにUIスレッドをブロックしないために使うアレね」ぐらいの認識。
これまでは「どうせ読み込み終わるまで次に進めないんだからUIなんてフリーズさせとけ!」の脳筋バカ思考だったので、ちょうどいい機会ということで勉強することに。
とりあえずasync/awaitだけでいい
↑にすべてが書いてある。
非同期にしたい関数の呼び出し元にawait
を付けて、呼び出し先の関数の戻り値をasync Task<T>
にする。基本これだけ。
Task.Run
とかWait
とかResult
要らんし使うな、ということらしい。
あとイベントハンドラのみ戻り値をasync void
に。その時はtry/catch
で例外をキャッチしよう。
まずこれを覚えた。
先日の事件もあり、Dellのプリインストールアプリを見直していた。
その中でDell SupportAssistという各種ドライバやBIOSを自動で更新してくれるアプリを起動してみたが、いくつかあるDell PCのうちメインPCだけ起動しない。
起動時に「再起動後に再度実行しろ」とエラーメッセージが出るが何度再起動してもダメ。
一度アンインストールしてみて再度インストールしようとしても途中でエラーが出て失敗。
だからPCの再起動は死ぬほどやったんだって...。どうすればええねん。