- 投稿日:2020-09-13T21:02:22+09:00
[Gnuplot] 標準出力に流れるバイナリデータを読み取って表示する
標準出力に流れるバイナリデータを読み取って表示する
標準出力に流れるデータを直接取得することによって、
ファイルを経由せずに、画像データ(PNG)を表示する方法を以下に記述します。var process = new Process() { StartInfo = new ProcessStartInfo { FileName = @".\gnuplot.exe", Arguments = "", CreateNoWindow = true, UseShellExecute = false, RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = false, } }; process.Start(); process.StandardInput.WriteLine("set term png enhanced size 640,480"); process.StandardInput.WriteLine("set output"); process.StandardInput.WriteLine("set palette defined(0'#000090',1'#000fff',2'#0090ff',3'#0fffee',4'#90ff70',5'#ffee00',6'#ff7000',7'#ee0000',8'#7f0000')"); process.StandardInput.WriteLine("set xrange[-2:2]"); process.StandardInput.WriteLine("set yrange[-2:2]"); process.StandardInput.WriteLine("set pm3d at bs"); process.StandardInput.WriteLine("set ticslevel 0.8"); process.StandardInput.WriteLine("set isosample 20,20"); process.StandardInput.WriteLine("set view 60,40"); process.StandardInput.WriteLine("splot exp(-x*x) * exp(-y*y)"); var reader = new BinaryReader(process.StandardOutput.BaseStream); var iend = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 }; var list = new List<byte>(); while (list.Skip(list.Count - iend.Length).SequenceEqual(iend) != true) { list.Add(reader.ReadByte()); } var image = Mat.FromImageData(list.ToArray(), ImreadModes.Color); pictureBox.Image = BitmapConverter.ToBitmap(image); // // [exit]を送信するまでは何度でもプロットでき、その都度、画像データを取得できます。 // process.StandardInput.WriteLine("exit"); process.Close();・ReadByte(...)やRead(...)は、ファイルの終端を認識できなかった場合に永久にデータ待ちをしてしまいます。
しかし、標準出力はタイムアウトを設定できないため、何らかの処理が必要となります。
以下に例を記述しますが、もっとスマートな方法があるかもしれません...Task<byte> task; while (list.Skip(list.Count - iend.Length).SequenceEqual(iend) != true) { if ((task = Task.Run(() => reader.ReadByte())).Wait(1000) == false) { break; } list.Add(task.Result); }byte[] buffer = new byte[1024]; Task<IEnumerable<byte>> task = null; while (list.Skip(list.Count - iend.Length).SequenceEqual(iend) != true) { if ((task = Task.Run(() => buffer.Take(reader.Read(buffer, 0, buffer.Length)))).Wait(1000) == false) { break; } list.AddRange(task.Result); }var task = Task.Run(() => { while (list.Skip(list.Count - iend.Length).SequenceEqual(iend) != true) { list.Add(reader.ReadByte()); } }); var taskTimeout = Task.Run(() => { int lastCount = -1; while(lastCount != list.Count) { lastCount = list.Count; Task.Delay(1000).Wait(); } }); Task.WaitAny(task, taskTimeout);・データ待ちの解除は、以下のようなコードで行います。
if (task.IsCompleted == false) { process.StandardInput.WriteLine("exit"); }その他
・画像データの出力が1枚だけの場合は以下の記述でも可能です。
// --- 省略 --- process.StandardInput.WriteLine("splot exp(-x*x) * exp(-y*y)"); process.StandardInput.WriteLine("exit"); var reader = new BinaryReader(process.StandardOutput.BaseStream); var list = new List<byte>(); byte[] buf = new byte[1024]; while (reader.Read(buf, 0, buf.Length) != 0) { list.AddRange(buf); } // --- 省略 ---
- 投稿日:2020-09-13T20:22:19+09:00
非同期処理とApplication.Shutdown()
はじめに
本記事はC# WPF.NetFlameworkを対象とした記事となります。非同期処理を実装するシステムにて、適切に非同期処理を破棄する終了処理のベストプラクティスを模索した時の記録(2020/9/13時点)を
個人用
にまとめます。非同期処理について
foreground / background thread 概要
マネージドスレッドは以下の二つに分類されます。
種類 説明 foreground thread マネージド実行環境を維持できる。 background thread マネージド実行環境を維持できない。つまりすべてのフォアグラウンドが停止すると、システムによって全てのバックグラウンドスレッドが停止させられる。 これはUIスレッドで非同期処理を実行した場合、非同期処理実行中にUIスレッドを停止したときの動作の違いを意味します。「background thread」はUIスレッド終了に伴い終了しますが、
「foreground thread」は終了しません。
foreground / background thread 判定方法
次に特定の非同期処理がどちらに該当するのかを確認します。
方法としては、実際に非同期処理実行中にUIスレッドを停止してみて確認することもできるかもしれませんが、Thread.IsBackgroundというプロパティを参照する
のが最も容易です。
しかし、Threadオブジェクトを生成せずに非同期処理を実行する場合は、非同期処理内で以下の記述をすることでThread.IsBackgroundの規定値を確認することができます。Console.WriteLine(Thread.CurrentThread.IsBackground);ちなみにThreadオブジェクトを生成した際の規定値は
False
、Task.Run()にて非同期処理を実行する場合はTure
が規定値となります。Applicationの終了について
ここまでUIスレッドと非同期スレットとの関係性についての注意事項を述べましたが、本題であるApplication終了時の関係性について触れていきます。先に結論を言うと、
Applicationが終了するとマネージドスレッドの種類に関係なく、非同期スレッドは終了します。
では、Applicationの終了について詳しく見ていきます。Window.Close()
基本的には、WPFアプリケーションテンプレートで作成した
MainWindow.xaml.csにてClose()メソッドを呼び出すことでApplicationは終了
します。ただしApp.xamlもしくはApp.xaml.csを編集した場合は、それに限りません。厳密にいうとApplication.MainWindowに設定されたWindowインスタンスがClose()されたときに、Application.ShutdownModeがOnLastWindowCloseに設定されていたかどうかによります。
Application.ShutdownMode
Application.ShutdownModeが規定値であるOnLastWindowCloseに設定されていた場合、Application.MainWindowに設定されたWindowインスタンスのClose()時にApplicationは終了されます。しかし
OnExplicitShutdownに設定されていた場合は、Close()=Applicationの終了とはなりません。
※結果的にAppicationが終了することはありますが、Applicationが生きたままとなる危険性があります。したがって後者の場合は、
Close()ではなく明示的にApplication.Shutdown()メソッドを呼び出す必要があります。
Application.MainWindow
ここでApplication.MainWindowにWindowインスタンスを設定する方法を見ていきます。Application.MainWindowにセットするには、
App.xaml
で行う方法とApp.xaml.cs
で行う方法の2通りあります。
テンプレートからWPFアプリケーションを作成した場合はApp.xaml
のApplicationのStartupUriにて記述されています。App.xaml<Application x:Class="HelloWorld.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> </Application.Resources> </Application>続いて、App.xaml.csでMainWindowを設定する方法を提示します。
App.xaml<Application x:Class="HelloWorld.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Startup="AppStartup"> <Application.Resources> </Application.Resources> </Application>App.xaml.cspublic partial class App : Application { void AppStartup(object sender, StartupEventArgs e) { MainWindow window = new MainWindow(); window.Show(); } }コチラの方法では
App.xaml
のStartupUriにて直接xamlを指定せずに、App.xaml.cs
のStartupイベント上にてWindowインスタンスを生成します。
ここで補足になりますが、上記ではApplication.MainWindowに明示的にセットしていません。しかし、実はApplication.Mainwindowに生成したMainWindowsインスタンスがセットされています。これは、最初にWindowsインスタンスを生成した際に自動でそのインスタンスがApplication.MainWindowにセットされる仕様のため、上記のような記述になっています。
したがって同イベント内でWindowインスタンスを2つ生成した場合は、最初に生成したWindowインスタンスがClose()した段階でApplicationが終了処理に入るので、以降の処理は実行されないことに注意してください。App.xaml.cspublic partial class App : Application { void App_Startup(object sender, StartupEventArgs e) { // Windowsインスタンスを生成した際に自動でそのインスタンスが // Application.MainWindowにセットされる。 Window firstWindow = new MainWindow(); Window secondWindow = new MainWindow(); firstWindow.ShowDialog(); // 移行の処理は実行されません。 secondWindow.ShowDialog(); } }まとめ
以上より、非同期処理を実装したアプリケーションは、以下に留意して実装することを結論としました。
- 非同期処理は理由のない限り、「background thread」で処理させる。
- マネージドスレッドの種類を問わず、Applicationを終了させることで非同期スレッドも終了させることができる。
- Application.ShutdownModeやAppication.MainWindowが編集されることを考慮して、基本的にはClose()ではなく明示的にApplication.ShutDown()を使用してApplication終了処理を記述する。
※ガベージコレクションについては話がややこしくなるので、ココでは触れない。
- 投稿日:2020-09-13T14:49:31+09:00
C#で<任意の数値型>のような型制約を実現する
AtCoderLibraryのC#移植版の開発において
Class<任意の数値型>
のような型を実現したいという意図があり、検討した結果を記します。解説用の例として最大公約数を計算する下記のようなプログラムを考えます。
class Program { static void Main() { System.Console.WriteLine(new Gcder(30030).Calc(42)); // → 42 System.Console.WriteLine(new Gcder(120).Calc(119)); // → 1 } } class Gcder { static int Gcd(int a, int b) => b > a ? Gcd(b, a) : (b == 0 ? a : Gcd(b, a % b)); public int a; public Gcder(int a) { this.a = a; } public int Calc(int b) => Gcd(a, b); }課題:数値計算はGeneric化できない
これをGeneric化しようとすると一筋縄では行きません。
b > a
,b == 0
,a % b
などが未定義なためです。class Program { static void Main() { System.Console.WriteLine(new Gcder<int>(30030).Calc(42)); System.Console.WriteLine(new Gcder<ulong>(30030).Calc(42)); } } class Gcder<T> where T : struct { static T Gcd(T a, T b) => b > a ? Gcd(b, a) : (b == 0 ? a : Gcd(b, a % b)); // この行がコンパイル不能 public T a; public Gcder(T a) { this.a = a; } public T Calc(T b) => Gcd(a, b); }C++ での実現
C++ではtemplateの条件式を使って表現可能です。
#include <type_traits> #include <iostream> template <class T> auto gcd(T a, T b) -> typename std::enable_if<std::is_integral<T>::value, T>::type { return b > a ? gcd(b, a) : (b == 0 ? a : gcd(b, a % b)); } int main() { std::cout << gcd(120, 96) << std::endl; std::cout << gcd(120ULL, 96ULL) << std::endl; // std::cout << gcd("120", 96) << std::endl; // Compilation error }解決:数値計算用のinterface
interface定義
数値計算用のinterfaceを用意すると良いです。
public interface INumOperator<T> : IEqualityComparer<T>, IComparer<T> where T : struct { T MinValue { get; } T MaxValue { get; } T Add(T x, T y); T Subtract(T x, T y); T Multiply(T x, T y); T Divide(T x, T y); T Modulo(T x, T y); bool GreaterThan(T x, T y); bool GreaterThanOrEqual(T x, T y); bool LessThan(T x, T y); bool LessThanOrEqual(T x, T y); } public readonly struct IntOperator : INumOperator<int> { public int MinValue => int.MinValue; public int MaxValue => int.MaxValue; public int Add(int x, int y) => x + y; public int Subtract(int x, int y) => x - y; public int Multiply(int x, int y) => x * y; public int Divide(int x, int y) => x / y; public int Modulo(int x, int y) => x % y; public bool GreaterThan(int x, int y) => x > y; public bool GreaterThanOrEqual(int x, int y) => x >= y; public bool LessThan(int x, int y) => x < y; public bool LessThanOrEqual(int x, int y) => x <= y; public int Compare(int x, int y) => x.CompareTo(y); public bool Equals(int x, int y) => x == y; public int GetHashCode(int obj) => obj.GetHashCode(); } // LongOperator, UIntOperator, ULongOperator なども同様に定義ここでポイントなのは、
IntOperator
はstruct
とすることです。詳細は後述します。完成形
class Program { static void Main() { System.Console.WriteLine(new Gcder<int, IntOperator>(30030).Calc(42)); System.Console.WriteLine(new Gcder<ulong, ULongOperator>(30030).Calc(42)); System.Console.WriteLine(new Gcder<int, IntOperator>(120).Calc(119)); System.Console.WriteLine(new Gcder<ulong, ULongOperator>(120).Calc(119)); } } class Gcder<T, TOp> where T : struct where TOp : struct, INumOperator<T> { static readonly TOp op; static T Gcd(T a, T b) => op.GreaterThan(b, a) ? Gcd(b, a) : (op.Equals(b, default) ? a : Gcd(b, op.Modulo(a, b))); public T a; public Gcder(T a) { this.a = a; } public T Calc(T b) => Gcd(a, b); }これで、
int
もulong
を対象にしたGeneric化ができました。考察
演算インターフェイスを struct で実装する理由
演算インターフェイスを struct で実装する理由は大きく2つあります。
- struct はnon-nullable
- struct が型引数に入ると型が展開されて高速
前者については、
static readonly TOp op;
を初期化せずに使えるというだけですので簡単です。後者が重要です。
演算型が class だった場合は
op.GreaterThan(b, a)
のような呼び出しは仮想化されているので、絶対にJIT最適化されません。
一方、 struct だとop.GreaterThan(b, a)
がJIT最適化によって展開可能になります。そのため、高速に動作するようになります。Benchmarks
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.1082 (1909/November2018Update/19H2)
Intel Core i7-4790 CPU 3.60GHz (Haswell), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.401
[Host] : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT
DefaultJob : .NET Core 3.1.7 (CoreCLR 4.700.20.36602, CoreFX 4.700.20.37001), X64 RyuJIT
Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated DirectInt 1.173 s 0.0046 s 0.0038 s - - - 304 B GenericInt 1.292 s 0.0054 s 0.0048 s - - - 2112 B GenericClassInt 2.361 s 0.0463 s 0.0747 s - - - 304 B
Code
class Program { static void Main() { BenchmarkRunner.Run<AclBench>(DefaultConfig.Instance.AddDiagnoser(MemoryDiagnoser.Default)); } } class GcderInt { static int Gcd(int a, int b) => b > a ? Gcd(b, a) : b == default ? a : Gcd(b, a % b); public int a; public GcderInt(int a) { this.a = a; } public int Calc(int b) => Gcd(a, b); } class Gcder<T, TOp> where T : struct where TOp : INumOperator<T>, new() { static readonly TOp op = new TOp(); static T Gcd(T a, T b) => op.GreaterThan(b, a) ? Gcd(b, a) : (op.Equals(b, default) ? a : Gcd(b, op.Modulo(a, b))); public T a; public Gcder(T a) { this.a = a; } public T Calc(T b) => Gcd(a, b); } public class ClassIntOperator : INumOperator<int> { public int MinValue => int.MinValue; public int MaxValue => int.MaxValue; public int Add(int x, int y) => x + y; public int Subtract(int x, int y) => x - y; public int Multiply(int x, int y) => x * y; public int Divide(int x, int y) => x / y; public int Modulo(int x, int y) => x % y; public bool GreaterThan(int x, int y) => x > y; public bool GreaterThanOrEqual(int x, int y) => x >= y; public bool LessThan(int x, int y) => x < y; public bool LessThanOrEqual(int x, int y) => x <= y; public int Compare(int x, int y) => x.CompareTo(y); public bool Equals(int x, int y) => x == y; public int GetHashCode(int obj) => obj.GetHashCode(); } public class AclBench { const int N = 10_000_000; [Benchmark] public long DirectInt() { var rnd = new System.Random(42); long sum = 0; var gcder = new GcderInt(2 * 3 * 5 * 7 * 9 * 11 * 13 * 17 * 19 * 23); for (int i = 0; i < N; i++) { sum += gcder.Calc(rnd.Next()); } return sum; } [Benchmark] public long GenericInt() { var rnd = new System.Random(42); long sum = 0; var gcder = new Gcder<int, IntOperator>(2 * 3 * 5 * 7 * 9 * 11 * 13 * 17 * 19 * 23); for (int i = 0; i < N; i++) { sum += gcder.Calc(rnd.Next()); } return sum; } [Benchmark] public long GenericClassInt() { var rnd = new System.Random(42); long sum = 0; var gcder = new Gcder<int, ClassIntOperator>(2 * 3 * 5 * 7 * 9 * 11 * 13 * 17 * 19 * 23); for (int i = 0; i < N; i++) { sum += gcder.Calc(rnd.Next()); } return sum; } } public interface INumOperator<T> : System.Collections.Generic.IEqualityComparer<T>, System.Collections.Generic.IComparer<T> where T : struct { T MinValue { get; } T MaxValue { get; } T Add(T x, T y); T Subtract(T x, T y); T Multiply(T x, T y); T Divide(T x, T y); T Modulo(T x, T y); bool GreaterThan(T x, T y); bool GreaterThanOrEqual(T x, T y); bool LessThan(T x, T y); bool LessThanOrEqual(T x, T y); } public readonly struct IntOperator : INumOperator<int> { public int MinValue => int.MinValue; public int MaxValue => int.MaxValue; public int Add(int x, int y) => x + y; public int Subtract(int x, int y) => x - y; public int Multiply(int x, int y) => x * y; public int Divide(int x, int y) => x / y; public int Modulo(int x, int y) => x % y; public bool GreaterThan(int x, int y) => x > y; public bool GreaterThanOrEqual(int x, int y) => x >= y; public bool LessThan(int x, int y) => x < y; public bool LessThanOrEqual(int x, int y) => x <= y; public int Compare(int x, int y) => x.CompareTo(y); public bool Equals(int x, int y) => x == y; public int GetHashCode(int obj) => obj.GetHashCode(); }余談
C# 10.0がターゲットの Type Classes という機能がまさにこれに近い機能みたいです。