アジョブジ星通信

進捗が出た頃に更新されるブログ。

それでも僕はWineでMonoを動かしたかった

Wine 上で動く Windows アプリを制御するにはどうしよう?と考えた結果、制御する側も Wine 上で動かせば Windows 上と同じように操作できるじゃないか!と思いやっていった記録。残念ながら、結局 Docker 上で動かすことはできなかったので、僕の意志を引き継いでくれる方、もしくは先行研究がありましたら教えてください。

というわけで、 Docker で動かすのを断念するまでに得た知見をまとめておきたいと思います。

環境

  • Ubuntu 17.04
  • Wine 2.14 win32 prefix
  • Mono 5.2.0 x86

wine-mono?それともスタンドアロンの Mono?

wine-mono は Mono のコアライブラリ + .NET Framework になりすますための mscoree.dll という構成になっています。つまり .NET の exe ファイルを起動しようとすると Mono を使ってくれる、という構成になります。

それに対して、 mono-project.com からダウンロードできる Mono の Windows 版は mono.exe を使ってアセンブリを実行する形をとります。つまり Linux で使っている Mono とまったく同じです。

これらふたつは共存することができるので、間接的に .NET Framework を必要とするアプリには wine-mono でうまくやっておいてもらいつつ、 .NET アセンブリだと最初からわかっているものは mono.exe で起動するという使い方ができるでしょう。

さて、最初は wine-mono で全部なんとかしようと思っていたのですが、厄介な問題にぶち当たりました。 wine-mono はアセンブリの厳密名を検証するようですが、回避するために App.config に <bindingRedirect> を書いても読み込んでくれないようです。というわけで、基本的には mono.exe を使うべきだということがわかりました。

続きを読む

I/O待ちのためのTaskとバックグラウンド処理のためのTask

ポエポエ~~

の一連のツイートのまとめでもしておこうかと。

I/O 待ちのための Task

I/O 処理を OS に投げて、完了を待機するのを抽象化した Task を「I/O 待ちのための Task」と呼んでいきます。身近かつ純粋な I/O 待ちを行う Task の例として、 FileStream を FileOptions.Asynchronous オプションをつけて開いたときの ReadAsync が挙げられます。

例として、 ReadAsync を await したときの挙動を見てみましょう。

async Task FileReadSample()
{
    const int bufSize = 10;

    // FileOptions を指定するために省略できないオプション大杉
    using (var fs = new FileStream(
        "test.txt",
        FileMode.Open,
        FileAccess.Read,
        FileShare.ReadWrite,
        bufSize,
        FileOptions.Asynchronous))
    {
        var buf = new byte[bufSize];

        var readBytes = await fs.ReadAsync(buf, 0, bufSize);

        Console.WriteLine(readBytes);
    }
}

「FileReadSample 続き」がどこで実行されるかは、このメソッドがどんな SynchronizationContext で呼び出されたかによりますが、とにかく OS からの非同期処理完了をスレッドプールが管理している I/O 完了ポートで待ち受け、 Task の完了を通知して、継続タスクを実行するという動きをします。

このとき ReadAsync 単体で見ると、 I/O 完了ポート用のスレッドが足りていれば、新しいスレッドを作成することも、スレッドプールのキューに新たにタスクを追加することもありません。つまり「I/O 待ちのための Task」は OS の非同期処理 API をラップし、継続タスクへの通知を行うだけのものといえます。

ただし、 await は SynchronizationContext が特別な場合(UIスレッドなど)を除き、続きをスレッドプールのキューに追加します。これはコールスタックが大きくなるのを防ぐためらしいですが、それが嫌なら ContinueWith で TaskContinuationOptions.ExecuteSynchronously を指定することで、完了通知を受け取ったスレッド上でそのまま続きが実行されます(本当?)。

より詳しく: Async訪ねて3000里

スレッドプールの有効利用

I/O 待ちのための Task は、スレッドプールを有効的に利用するために使います。

スレッドプールは短時間で終わる処理が大量にあるときに効率良くスレッドを利用する手段ですから、 I/O 待ちでスレッドプールのスレッドをブロックしてしまうのはスレッドプールの効率を落としてしまいます。そこで I/O を非同期で行い、完了を待っている間にキューに溜まった他の処理を実行できるように、I/O 待ちのための Task を利用します。

逆に、スレッドプール内で動いている Task でバックグラウンド処理のための Task を await するのは得策ではありません。なぜなら、そのまま続行できる処理をわざわざスレッドプールのキューに入れることになり、切り替えコストが発生するからです。

スレッドプールを有効利用することが求められるのは、web アプリケーションとライブラリです。

まず、 web アプリケーションでは、リクエストごとの処理がスレッドプールのキューに追加されます。ここでスレッドをブロックするようなコードを書いてしまうと、十分な性能を出すために多くのスレッドが必要となってしまいます。スレッドプールに割り当てるスレッドが多くなりすぎると、メモリ使用量がどんどん増えていってしまいますし、スレッドの管理コストも上がってしまいます。このような理由から、I/O 待ちのための Task を利用すると必要なスレッド数を減らせて、リソースを削減できます。

次にライブラリですが、ライブラリはあらゆる .NET アプリケーションから使われるので、 web アプリケーションから使われることを考えると同じことが言えます。

バックグラウンド処理のための Task

UI スレッドのフリーズを防ぐため、または並列処理のために処理を分割したものを「バックグラウンド処理のための Task」と呼んでいきます。バックグラウンド処理のための Task は、 CPU で大量の計算を行うときに Task.Run などを使って作成します。

「~Async」メソッドの罠

「~Async」という名前がついているメソッドは、一般的なライブラリの場合 I/O 待ちのための Task を表していることが多いです。理由は「スレッドプールの有効活用」で示した通りです。

I/O 待ちのための Task を返すメソッドが実際に非同期で処理するのは I/O だけです。だから、 I/O が発生する前に重い計算をしていても、それはメソッドを呼び出したスレッドで実行されているかもしれません。もし、 GUI アプリケーションでそのようなメソッドを UI をフリーズさせずに実行したいならば、

var taskResult = await Task.Run(async () =>
{
    var fooResult = await FooAsync();
    // 後処理
});
// taskResult を UI に反映したり

といったように、バックグラウンド処理のための Task に変換する必要があります。

また、 Task を返すメソッドだからといって、必ず非同期処理が行われるわけではありません。例えば、条件によって I/O が発生しないメソッドは、条件を満たすときに完了済みタスクを返します。 await は Awaiter の IsCompleted が true のとき、すなわち完了済みタスクが返ってきたときには、現在のスレッドのまま処理を続行します。このことを覚えておかないと、「メソッド内の1回目の await で ConfigureAwait(false) を指定したから、2回目からは要らないよね」とか言って死にます。

バックグラウンド処理のための Task の乱用防止

スレッドを切り替えずに済む処理をわざわざ Task にする必要はありません。処理を別スレッドに移譲する必要があるのは GUI アプリケーションくらいでしょう。ライブラリ開発者はこのことを頭に入れて、 I/O に関する部分だけを TaskAsync メソッドとして公開するべきです。そして、 GUI アプリケーション開発者は必要に応じて自分で重い処理を Task で包むようにします。このようにすることで、スレッドプールを有効に利用したコードが書けるのではないかと思います。

TargetFrameworksからビルドできそうなフレームワークだけビルドする

これから1ヶ月以上ブログ書いてなかった。というわけで、これの続き。

この記事では PCL や Xamarin 用のアセンブリdotnet build でビルドできるようにしましたが、これだと MS の不自由アセンブリや Xamarin がインストールされていないとエラーになってしまうので、なんとかして警告レベルに落としたいので、やっていきます。

まずは対処前の状況。こんな感じのプロジェクトをビルドしていきます。

<TargetFrameworks>netstandard1.1;netstandard1.5;portable45-net45+win8+wpa81;monoandroid10;monotouch10;xamarinios10</TargetFrameworks>

https://github.com/CoreTweet/CoreTweet/blob/1d53cb7888eda55e681ed865538d95c0eb394da8/CoreTweet/CoreTweet.csproj

するとこう。

PCL がコケたところでビルドが止まってしまっていますね。というか並列ビルドしてくれよ。

では、 TargetFrameworks からビルドできなさそうなフレームワークを消していくコードを追加していきましょう。やり方を言葉で表すとこんな感じ。

  1. まとめてビルドするやつ(DispatchToInnerBuilds ターゲット)の実行前にフック
  2. 次の手順をすべてのフレームワークに実行(といっても netstandard や netcoreapp は絶対ビルドできるし、次の手順には失敗するので素通りさせる)
    1. GetReferenceAssemblyPaths タスクを叩いてみる
    2. 結果が空でないなら TargetFrameworks に追加する

以上を全フレームワークに実行します。

というわけで実際のコードはこちら。

<Target Name="CheckAvailableFrameworks" BeforeTargets="DispatchToInnerBuilds">
  <ItemGroup>
    <!-- _TargetFramework という名前は DispatchToInnerBuilds 内で使われているので、被らないようにアンダーバー増やした -->
    <__TargetFramework Include="$(TargetFrameworks)" />
    <!-- netcoreapp もあるならそれもちゃんと Condition に書きましょう -->
    <_OldTargetFramework Include="@(__TargetFramework)" Condition="!$([System.String]::Copy('%(Identity)').StartsWith('netstandard'))" />
    <_NewTargetFramework Include="@(__TargetFramework)" Condition="$([System.String]::Copy('%(Identity)').StartsWith('netstandard'))" />
  </ItemGroup>
  <!-- TargetFramework プロパティをセットして CheckCompilability を呼び出す -->
  <!-- TargetFramework プロパティがセットされていると、うまいこと TargetFrameworkMoniker プロパティがセットされるのを利用していきたい -->
  <MSBuild Projects="$(MSBuildProjectFile)"
           Targets="CheckCompilability"
           Properties="TargetFramework=%(_OldTargetFramework.Identity)">
    <!-- 結果をとりあえずアイテム化しておいて -->
    <Output TaskParameter="TargetOutputs" ItemName="_NewTargetFramework" />
  </MSBuild>
  <PropertyGroup>
    <!-- 強引にプロパティに変換する -->
    <TargetFrameworks>@(_NewTargetFramework)</TargetFrameworks>
  </PropertyGroup>
</Target>

<Target Name="CheckCompilability" Returns="$(CompilableFramework)">
  <!-- ContinueOnError="true" によってエラーを警告化 -->
  <!-- これで完全勝利 -->
  <GetReferenceAssemblyPaths TargetFrameworkMoniker="$(TargetFrameworkMoniker)"
                             RootPath="$(TargetFrameworkRootPath)"
                             ContinueOnError="true">
    <Output TaskParameter="ReferenceAssemblyPaths" PropertyName="_ReferenceAssemblyPaths" />
  </GetReferenceAssemblyPaths>
  <PropertyGroup Condition="'$(_ReferenceAssemblyPaths)' != ''">
    <!-- 成功したときだけ Returns に追加されるってわけ -->
    <CompilableFramework>$(TargetFramework)</CompilableFramework>
  </PropertyGroup>
</Target>

これで警告になりました。めでたしめでたし。

新しい csproj に負けないレガシーな心を持ち続けて云々

Sdk="Microsoft.NET.Sdk" なプロジェクトファイルで使える小ネタ集です。

NETStandard.Library への参照をいじる

PackageReference を何ひとつ書かなくても NETStandard.Library を勝手にダウンロードしてくるのは、手軽にコードが書けるという点ではいいかもしれないけれど、やはり無駄なパッケージ参照があるというのは許せないものなので、消していきましょう。

DisableImplicitFrameworkReferences というプロパティがあり、これを true にするとデフォルトの参照が消えます。デフォルトの参照は以下の通り(targets ファイルを漁るとでてくる)で、これらが全部消えます。つまり .NET Framework なら mscorlib だけ、 .NET Core なら無です。

  • .NETStandard (Microsoft.NET.Sdk.DefaultItems.props)
    • NETStandard.Library 1.6.1
  • .NETCoreApp (Microsoft.NET.Sdk.DefaultItems.props)
  • .NETFramework (Microsoft.NET.Sdk.BeforeCommon.targets)
    • System
    • System.Data
    • System.Drawing (マジかよ)
    • System.Xml
    • System.Core
    • System.Runtime.Serialization
    • System.Xml.Linq
    • System.Numerics
    • System.IO.Compression.FileSystem

NETStandard.Library のバージョンは NetStandardImplicitPackageVersion プロパティを、 Microsoft.NETCore.App のバージョンは RuntimeFrameworkVersion プロパティを書き換えることで、自由に変更することができます。

netcoreapp1.0 をターゲットにすると Microsoft.NETCore.App のバージョンは 1.0.4 になるけど、 1.1.1 に変えても問題なく動くんだよな……。

サンプルコードとして、 NETStandard.Library を消して、 System.Console (とその依存パッケージ)だけを参照するプロジェクトです。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard1.4</TargetFramework>
    <DisableImplicitFrameworkReferences>true</DisableImplicitFrameworkReferences>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="System.Console" Version="4.3.0" />
  </ItemGroup>

</Project>

Portable Class Library や Xamarin クラスライブラリをビルドする

プロジェクトファイルにちょっと手を加えれば、インストールされているフレームワークなら何でもビルドできます。たぶん。

TargetFramework, TargetFrameworkIdentifier, TargetFrameworkVersion、あとプロファイル指定が必要なら TargetFrameworkProfile というプロパティを設定すれば、勝手にアセンブリ解決してビルドしてくれます。

例えば、いにしえのフレームワーク軍を相手する portable40-net40+sl5+win8+wp8+wpa81 ならこんな感じ

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>portable40-net40+sl5+win8+wp8+wpa81</TargetFramework>
    <TargetFrameworkIdentifier>.NETPortable</TargetFrameworkIdentifier>
    <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
    <TargetFrameworkProfile>Profile328</TargetFrameworkProfile>
  </PropertyGroup>

  <ItemGroup>
    <Reference Include="System" />
  </ItemGroup>

</Project>

mscorlib 以外は勝手に参照されないので、必要に応じて Reference を書いてあげる必要があります。

Xamarin も同様にできますが、ファサードアセンブリを使うために ImplicitlyExpandDesignTimeFacades プロパティを true にしておく必要があるのと、 Visual Studio 2017 からアセンブリの場所が変わったので、それに対する対応を入れる必要があります。

Visual Studio 2015 までは C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework に Xamarin のアセンブリがありましたが、 2017 では C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE\ReferenceAssemblies\Microsoft\Framework になりました。 Microsoft.NET.Sdk では前者のパスを探しにいくので、 2017 だけがインストールされている環境ではアセンブリが見つかりません。

その対策が入っている Xamarin.Android.Sdk.props をパクりつつ、 Xamarin クラスライブラリをつくるプロジェクトファイルはこんな感じになります。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>monoandroid70</TargetFramework>
    <TargetFrameworkIdentifier>MonoAndroid</TargetFrameworkIdentifier>
    <TargetFrameworkVersion>v7.0</TargetFrameworkVersion>
    <TargetFrameworkRootPath Condition="'$(VsInstallRoot)' != ''">$(VsInstallRoot)\Common7\IDE\ReferenceAssemblies\Microsoft\Framework\</TargetFrameworkRootPath>
    <ImplicitlyExpandDesignTimeFacades>true</ImplicitlyExpandDesignTimeFacades>
  </PropertyGroup>

  <ItemGroup>
    <Reference Include="Mono.Android" />
  </ItemGroup>

</Project>

Xamarin Android プロジェクトで、このプロジェクトを参照に加えて実行してみましたが、問題なく動作しました。

VsInstallRoot プロパティは Visual Studio についている MSBuild でしか設定されていないので、 .NET Core SDKMSBuild でビルドした場合には 2015 までのアセンブリの場所を見に行きます。

MySQL Connector/.NET の TreatTinyAsBoolean のバグと Dapper

MySQL Connector/.NET、長いので MySql.Data と呼びます。

バグについて

MySql.Data の接続文字列で使用できるオプションとして、「TreatTinyAsBoolean」または「Treat Tiny As Boolean」というものがあります。これはデフォルトで有効になっていて、このオプションが有効のときは TINYINT(1) のカラムを Boolean 型として扱います。

で、常に Boolean として扱ってくれるかというとそうではないという問題があります。 TINYINT(1) 型のカラムに null が入っていると、それ以降のレコードは Boolean ではなく SByte になります。これによってありがた迷惑な機能から、ただの迷惑な機能に昇格しましたね。おめでとう。

続きを読む

自分のスキルセットを確認してみる

シャワー浴びながらふと気づいたこと、自分には何ができるのだろうか。いつも自己紹介が苦手といってきたけれど、いい加減自分をアピールできる材料くらい知っておかないと思いリストアップしてみようと思います。

続きを読む

書きはじめ

あけましたね、おめでとうございます。2016年の振り返りと2017年の抱負を豊富に書くのが恒例行事らしいのでやるか。知らんけど。AbemaTV ではっぴぃにゅうにゃあしながら書いてるので自分でも何書いてるかわからなくなりそう。

2016年の振り返り

去年のことは去年のうちに書き捨てておくべきでしたが、ソフィーのアトリエやってガキの使い見てたら年が変わってたので許してほしい。あと記憶力ゼロ太郎なので覚えてない。

大学に入学した

北千住駅の目の前のオタクが詰め込まれているあそこです。レポートを手書きさせられています。

勉強意欲のほうですが、中学生のころ並には回復してきました。高校生のときは受験受験いわれて完全にやる気を失っていたのでね、多少マシになった(勉強したいとは言ってない)。

あと帰宅部コミュ障なのでお友達が少ない。ほかに書くことないのかボケ。

アルバイ

正直クソツイート太郎なので、僕のツイアカウントを某社リストに入れられるのつらいんですけど、って感じには隠れていたいんですけど、ついにコード書いてお金がもらえるようになりました。こういうことしてます。

仕事力(いろいろな要素があります)高いマンを見てると、人間力がほしい~って気持ちになりますね(語彙力)。とりあえず役には立てているらしいので、精進していきます。

ほかなにしたっけ

パフォーマンスやくざになったり、 null 撲滅過激派になったりした記憶があります。みなさんも Rust を書いて安全なプログラミングについて考えましょう、という感じです。

ほんと日本語力も記憶力もねーなこいつ。

2017年だよ

今週のお題「2017年にやりたいこと」

宣言なんてするもんじゃなかった。

いつも通り C# 界隈の情報を追いつつ、 Rust 界隈の情報も得ていく活動を続けながら、大学で新しい技術を身に着けていきたいなぁといったところです。今年もよろしくお願いします。