WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

環境別の appsettings.json

by WebSurfer 23. September 2020 12:10

開発環境と運用環境では、通常、接続文字列やエラー表示などが異なります。.NET Framework 版の ASP.NET Web アプリではそれらの設定は web.config ファイルに含まれており、運用環境にデプロイする際は該当部分を書き換える必要があります。

Visual Studio で開発を行う場合は、運用環境にデプロイする際書き換える部分をあらかじめ Web.Release.config というファイルに作っておき、Visual Studio の発行ツールを使ってデプロイ時に書き換えるということが可能です。

Web.Release.config

上の画像は Visual Studio Community 2019 のテンプレートを使って作成した ASP.NET Web Forms アプリで自動生成される Web.Release.config です。接続文字列とエラー表示の設定を書き換えるためのひな型が含まれています。

しかし、Core 版には .NET Framework 版と同じ機能はなさそうです。では、どうするかですが、以下の Microsoft のドキュメントに説明がありました(日本語版は翻訳がダメなので意味が分からないときは英語版を見てください)。

ASP.NET Core の構成

ASP.NET Core で複数の環境を使用する

appsettings.json に加えて、必要に応じて appsettings.Development.json, appsettings.Staging.json, appsettings.Production.json を追加し、それにそれぞれの環境専用の接続文字列などの設定を書きます。(注: Visual Studio のテンプレートを使うと appsettings.json と appsettings.Development.json は自動的に作成されプロジェクトに含まれているはずです)

appsettings.json

そして、例えば接続文字列を環境によって変えたい場合、appsettings.Development.json, appsettings.Staging.json, appsettings.Production.json にそれぞれの環境の接続文字列を書けば、環境変数 ASPNETCORE_ENVIRONMENT の値 (Development, Staging, Production のいずれか)によってファイルを選んでそれから接続文字列を取得してきます。

結果は appsettings.json の設定プラス appsettings.環境.json の設定になります。appsettings.json の設定を丸ごと appsettings.環境.json の設定に置き換えるということはしませが、設定がダブった場合(同じ名前のキーがあった場合)は、上に紹介した記事「ASP.NET Core の構成」に書いてある通り、appsettings.json の方をオーバーライドします。

例えば、開発環境においては、appsettings.Development.json 構成が appsettings.json の値を上書きします。運用環境では、appsettings.Production.json 構成が appsettings.json の値を上書きします。

なお、環境変数 ASPNETCORE_ENVIRONMENT が設定されてない場合は、上に紹介した記事「ASP.NET Core で複数の環境を使用する」に書いてあるように、デフォルトで Production になることに注意してください。

では、環境変数 ASPNETCORE_ENVIRONMENT をどのように設定するかですが・・・

開発環境では Visual Studio のテンプレートを使ってプロジェクトを作ると自動的に生成される launchSettings.json ファイルに "Development" と設定されています。

launchSettings.json

(注: 上の画像で赤枠で囲った方は Visual Studio から実行した場合デフォルトになる IIS Express を使ってインプロセスホスティングで実行する場合の設定です。下の方は Kestrel で実行する場合の設定です)

"Development" を "Staging", "Production" に変更して試してみると、上に書いた通り、取得してくる先の appsettings.環境.json が異なるのが分かるはずです。

運用環境では launchSettings.json ファイルは使いません。デプロイもされません。ではどうするかですが、そもそも環境変数なので、上に紹介した記事「ASP.NET Core で複数の環境を使用する」の「環境を設定する」のセクションに書いてあるようにして設定できるそうです。

または、ASPNETCORE_ENVIRONMENT が設定されてなければデフォルトで Production なので何も設定しないということでも良いかもしれません。

上に書いた appsettings.json の設定で接続文字列は環境によって書き換えできますが、エラー表示の設定の変更はできないので注意してください。それは Startup.cs の Configure メソッドで行います(HTTP 要求を処理するパイプラインを構成するためのミドルウェアの一部として登録)。

Visual Studio のテンプレートを使ってプロジェクトを作成すると、Startup.cs ファイルに以下のような Configure メソッドが自動的に生成されるはずです(日本語のコメントは自分が追加したものです)。

Startup.cs

環境変数 ASPNETCORE_ENVIRONMENT が Development に設定されていると env.IsDevelopment() が true になって、開発用の詳細エラーページが表示されるようになります。

Tags: , , ,

CORE

ConfigureAwait によるデッドロックの回避

by WebSurfer 21. September 2020 13:22

先の記事「await と Task.Result によるデッドロック」の続きです。この記事には、先の記事で述べたデッドロックを回避する方法を書きます。

(5) ConfigureAwait

Task.ConfigureAwait(Boolean) メソッドを使って、先の記事で述べた Task.Result プロパティを使ったコードにおけるデッドロックを回避することができます。(UI がフリーズするのは避けられませんが)

ConfigureAwait メソッドの使用

Microsoft のドキュメント「非同期プログラミングのベストプラクティス」に以下の説明があります。

"既定では、未完了の Task を待機するときは、現在のコンテキストがキャプチャされ、Task が完了するときのメソッドの再開に使用されます。 ・・・中略・・・ GUI アプリケーションと ASP.NET アプリケーションには、一度に実行するコードを 1 つのチャンクに限定する SynchronizationContext があります。await が完了するときは、キャプチャしたコンテキスト内で async メソッドの残りを実行しようとします。"

初めは Task.Result で待機しているスレッドを await 終了後に使おうとするからデッドロックが起こるのだろうと思っていましたが、それでは ASP.NET のケースが説明できません。ASP.NET で非同期にする目的はスレッドプールのスレッドの有効利用で、await 前までに使っていたスレッドはスレッドプールに戻し、await が完了後の処理はスレッドプールから新たにスレッドを取得して行いますので(ASP.NET の場合 await 前後でスレッドが異なるということ)���

少なくとも ASP.NET の場合、使われるスレッドが await 前後で同じかどうかはデッドロックに関係なく、上に書いた「一度に実行するコードを 1 つのチャンクに限定する」が関係しているようです。

ということは、GUI アプリ / ASP.NET アプリどちらにせよ、await が完了した後の残りの処理を、await で待機する際にキャプチャした「現在のコンテキスト」ではなく、別のコンテキストで行えばデッドロックにはならないはずです。

Microsoft のドキュメント「非同期プログラミングのベストプラクティス」に以下のように書いてあります。

"ConfigureAwait(continueOnCapturedContext: false) を追加すると、デッドロックが回避されます。この場合、await が完了するとき、スレッドプールのコンテキスト内で async メソッドの残り処理の実行が試みられます。このメソッドは完了でき、返されたタスクを完了するため、デッドロックは発生しません。"

というわけで、ConfigureAwait メソッドを使って以下のコードで試してみました。上の画像を表示したものです。

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsAsyncTest
{
    public partial class Form3 : Form
    {
        public Form3()
        {
            InitializeComponent();
        }

        // Task.Result 使用
        private void button1_Click(object sender, EventArgs e)
        {
            label1.Text = "UI スレッド、ID=" + 
                Thread.CurrentThread.ManagedThreadId;

            label2.Text = TimeCosumingMethod().Result;

            label1.Text += " / ID=" +
                Thread.CurrentThread.ManagedThreadId;
        }

        // await 使用
        private async void button2_Click(object sender, EventArgs e)
        {
            label1.Text = "UI スレッド、ID=" +
                Thread.CurrentThread.ManagedThreadId;

            label2.Text = await TimeCosumingMethod();

            label1.Text += " / ID=" +
                Thread.CurrentThread.ManagedThreadId;
        }

        private async Task<string> TimeCosumingMethod()
        {
            await Task.Delay(3000).ConfigureAwait(
                continueOnCapturedContext: false);

            return "TimeCosumingMethod の戻り値、ID=" +
                Thread.CurrentThread.ManagedThreadId;
        }
    }
}

上のコードのように ConfigureAwait(continueOnCapturedContext: false) を追加すると、TimeCosumingMethod メソッドで await Task.Delay(3000) が完了した後の残りの処理は、await で待機する際にキャプチャした「現在のコンテキスト」ではなく、スレッドプールのコンテキスト内で実行されるためデッドロックは回避できるということのようです。

上の画像を見てください。UI スレッドの ManagedThreadId(上の画像では ID=1)と TimeCosumingMethod メソッドの残りを実行したスレッド(上の画像では ID=4)が異なっています。

ただし、Task.Result を使う方は UI がフリーズするのは避けられません。一方、await を使う方は UI はフリーズしません(メッセージループは常に動いていて、マウスのクリックなどのユーザーイベントを処理して UI に反映してくれます)。

理由は、Task.Result は UI スレッドをブロックして TimeCosumingMethod メソッドが完了するのを同期的に待機するのに対して、await は呼び出し元に処理を戻して非同期的に待機するからのようです。

@IT の記事「第2回 非同期メソッドの構文」に以下の説明があります。

"await という名前から、「渡した非同期タスクが完了するまで、呼び出し元スレッドをブロックして待機する」というように感じるかもしれないが、実際はそうではない。await 演算子の意味は、「待っているタスクがまだ完了していない場合、メソッドの残りをそのタスクの『継続』として登録して呼び出し元に処理を戻し、タスクが完了したら登録しておいた継続処理を実行すること」なので注意が必要だ。"

Windows Forms アプリでは「Application.Run メソッド」によって UI スレッドで標準のメッセージループの実行が開始されるそうです。

なので、Task.Result を使う方は、UI スレッドがブロックされている間(上のコード例では約 3 秒間)はメッセージループは動いておらず、マウスのクリックなどのユーザーイベントには無反応(UI がフリーズ状態)になるということのようです。

Tags: , , , ,

.NET Framework

await と Task.Result によるデッドロック

by WebSurfer 20. September 2020 12:50

先の記事「非同期プログラミング」の続きで、非同期プログラミングに関して調べたことを備忘録的に書いたものです。

(4) デッドロック

先の記事の「(3) 訳が分からなかった話」に書いた Task.Result プロパティを使ったコードを Windows Forms のような GUI アプリに使うとデッドロックになります。(注: 先の記事のコンソールアプリではデッドロックになることはありません。理由後述)

Windows Forms アプリのコードでそのあたりを分かりやすく書くと以下のような感じです。コメントに「// デッドロック」と書いた方のメソッドを実行するとデッドロックが発生してアプリは固まってしまいます。

using System;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsAsyncTest
{
    public partial class Form3 : Form
    {
        public Form3()
        {
            InitializeComponent();
        }

        // デッドロック        
        private void button1_Click(object sender, EventArgs e)
        {
            label1.Text = "";
            string str = TimeCosumingMethod().Result;
            label1.Text = str;
        }

        // 正常動作
        private async void button2_Click(object sender, EventArgs e)
        {
            label1.Text = "";
            string str = await TimeCosumingMethod();
            label1.Text = str;
        }

        private async Task<string> TimeCosumingMethod()
        { 
            await Task.Delay(3000);
            return "TimeCosumingMethod の戻り値";
        }
    }
}

ちなみに ASP.NET Web アプリでも、Task.Result を使った同様なコードで、デッドロックが発生してアプリは固まってしまいます。

その理由は Microsoft のドキュメント「非同期プログラミングのベストプラクティス」の「すべて非同期にする」のセクションに書いてあります。

・・・が、自分はその説明を読んでもよく分かりませんでした。(汗) 自分の独断と偏見による個人的解釈まじりですが、以下のようなことになっているのであろうと想像しています。

Microsoft のドキュメント「Task<TResult>.Result プロパティ」と「Task.Wait メソッド」に以下の説明があります。

"Task.Result プロパティの get アクセサーにアクセスすると、非同期操作が完了するまで呼び出し元のスレッドがブロックされます。これは、Wait メソッドを呼び出すことと同じです。"

"Wait は、現在のタスクが完了するまで呼び出し元のスレッドを待機させる同期メソッドです。"

また、Microsoft のドキュメント「非同期プログラミングのベストプラクティス」に以下の説明があります。

"既定では、未完了の Task を待機するときは、現在のコンテキストがキャプチャされ、Task が完了するときのメソッドの再開に使用されます。 ・・・中略・・・ GUI アプリケーションと ASP.NET アプリケーションには、一度に実行するコードを 1 つのチャンクに限定する SynchronizationContext が使われます。await が完了するときは、キャプチャしたコンテキスト内で async メソッドの残りを実行しようとします。しかし、このコンテキストは既にその内部にスレッドを持っており、これは asyncメソッドが完了するのを (同期して) 待機します。それらは、それぞれもう一方を待機し、デッドロックを引き起こします。"

ということで、以下のようになっているのだろうと思いました。

  1. まず、上のコードの「デッドロック」とコメントしてある button1_Click メソッドに注目。その中の Task.Result プロパティを使っているところで、呼び出し元のスレッド(UI スレッド)は TimeCosumingMethod メソッドが完了するのを同期的に待機する。

    注: 上に書いた公式ドキュメントには Task.Result で「非同期操作が完了するまで呼び出し元のスレッドがブロックされます」とありますが、実際にコードを書いて検証してみると、呼び出し先の TimeCosumingMethod メソッドの await の前まで呼び出し元のスレッド(上の例では UI スレッド)で実行され、await の行に制御が移るとそこでデッドロックになります。
  2. 呼び出された TimeCosumingMethod メソッドに制御が移り、その中の await Task.Delay(3000); の await で待機するとき現在のコンテキストがキャプチャされ、await が完了した後はキャプチャしたコンテキストで残りを実行しようとする。  
  3. Windows Forms アプリでは、先の記事の「(2) Windows Forms アプリの ManagedThreadId」の例のとおり、スレッドの ManagedThreadId の値は変わらない。即ち、await で待機するときキャプチャする「現在のコンテキスト」および await 完了後に使われるキャプチャしたコンテキストでは同じ UI スレッドが使われ続けている。
  4. ということは、TimeCosumingMethod メソッドで await 完了後に使われる「キャプチャした現在のコンテキスト」のスレッドは、ステップ 1 で待機しているスレッドと同じということになる。
  5. ステップ 1 で待機しているスレッドは TimeCosumingMethod メソッドが完了しないと解放されないが、TimeCosumingMethod メソッド内の await が完了した時点では解放されていないので、その場所で開放されるのを永遠に待つことになってデッドロックに陥る。    

Windows Forms のような GUI アプリの場合は、実際にコードを書いての検証結果から、上に書いた通り UI スレッドが使われるのを確認しており、上記 1 ~ 5 で説明になっていると思います。

が、ASP.NET のケースが上記 1 ~ 5 では説明できません。ASP.NET で非同期にする目的はスレッドプールのスレッドの有効利用で、await 前までに使っていたスレッドはスレッドプールに戻し、await 完了後の処理はスレッドプールから新たにスレッドを取得して行いますので。(ASP.NET の場合 await 前後でスレッドが異なるということです)

Windows Forms アプリと異なり、ASP.NET アプリでは使うスレッドがどうなっているかより「一度に実行するコードを 1 つのチャンクに限定する」の方が関係しているのかも。つまり、以下のようなことではないかと思っています。

まず、button1_Click メソッドの Task.Result で 1 つの同期ブロックが待機中。呼び出された TimeCosumingMethod メソッドの await で待機する際にキャプチャされた「現在のコンテキスト」で await 完了後の同期処理を実行しようとする。しかし「現在のコンテキスト」では一度に実行するコードは 1 つのチャンクに限定されている。なので、Task.Result での待機が終わるまで await 完了後の同期処理は実行できない。結果デッドロックに陥る。

Windows Forms アプリでデッドロックする理由も上記のようなことなのかもしれません。が、今はこれ以上調べる気力がないです。もし何か分かったら追記します。


一方、コンソールアプリでは SynchronizationContext.Current は null になります。ということは await で待機する際にキャプチャできる「現在のコンテキスト」は存在せず、await 完了後の残りの処理は ASP.NET や Windows Forms アプリとは異なるようです。「非同期プログラミングのベストプラクティス」に以下のように書いてあります。

"コンソールアプリケーションでは、一度に 1 つのチャンクに制限する SynchronizationContext ではなく、スレッドプールを備えた SynchronizationContext を使用するため、await が完了するとき、スレッド プールのスレッドで async メソッドの残り処理のスケジュールが設定されます。"

上のステップ 2 のところで TimeCosumingMethod メソッドの残りを実行するのは「スレッドプールを備えた SynchronizationContext」になるようです。await で待機するときにキャプチャした「現在のコンテキスト」ではないのでデッドロックにならないということのようです。


最後に、上のサンプルコードで「// 正常動作」とコメントしたように Task.Result を使わないで 2 ヶ所で await した場合はデッドロックにはならないのは何故でしょう?

参考にさせていただ記事「ASP.NET の非同期でありがちな Deadlock を克服する」に以下のように書いてありました。

"AspNetSynchronizationContext に Post された 2 つの同期ブロックを、例の医者と患者の方式で順番に処理してくれるから"

・・・が、正直言ってその説明では分かりませんでした。(汗) はっきりした理由が分かったら追記します。


ConfigureAwait によるデッドロックの回避に続く。

Tags: , , , , ,

.NET Framework

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  October 2020  >>
MoTuWeThFrSaSu
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar