非同期プログラミングおよびパイプライン処理

非同期プログラミングおよびOracle Databaseパイプライン処理では、パフォーマンスのボトルネックが取り除かれ、全体的な応答性が改善されて、同期アプリケーションを制限できます。

リリース23ai以降の管理対象ODP.NETおよびODP.NET Coreでは、非同期プログラミングとデータベース・パイプライン処理が両方ともサポートされています。これらのテクノロジはどちらも、開発者が簡単に習得し既存のODP.NETアプリケーションまたは新しいODP.NETアプリケーションに組み込むことができます。

非同期プログラミング

接続のオープン、問合せの実行、データの読取りなどの同期プロバイダ・データベース処理は、完了までにかなりの時間がかかる場合があります。シングルスレッド・アプリケーションは、元の処理が終了するまで待機し他の処理をブロックします。一方、長時間実行される処理をバックグラウンド・スレッドに割り当てると、フォアグラウンド・スレッドを初めから終わりまでアクティブのままにできます。長時間実行される処理によって他のアプリケーション処理がブロックされることがなくなります。それにより、非同期動作の使用による全体的なパフォーマンス向上が実現されます。

非同期処理は、すぐに結果または完了が必要にならない、長時間実行される処理に最適です。これは、ユーザー・インタフェースでできるだけ高い応答性を維持する必要がある場合、または非同期処理が完了したかどうかにかかわらず同時に他の処理を実行できる場合に使用します。

.NETには、言語レベルの非同期プログラミング・モデルがあります。それによって、複数のコールバックをうまく調整することや非同期ライブラリに従うこと(このことにより、従来は非同期.NETコードが複雑になっていた)なく非同期コードを記述できます。このモデルはタスクベースの非同期パターン(TAP)と呼ばれており、開発者には、以前の非同期パターンと比べ最も開発が簡単であると知られています。

TAPでは、async修飾子を使用して非同期メソッドを指定します。非同期メソッドをコールすると、タスクが返されます。await演算子がそのタスクに適用されると、現在のメソッドはすぐに終了します。そのタスクが終了すると、同じメソッドで実行が再開されます。最も重要なのは、非同期メソッドをコールしてもスレッドが追加で割り当てられることはないということです。非同期機能を選択的に既存のODP.NETアプリケーションに追加すると、実行時のユーザー・エクスペリエンスを反復的に改善できます。

管理対象ODP.NETおよびODP.NET Coreでは、バージョン23以降でTAPがサポートされています。この機能は、Oracle Database 19c以上との下位互換性があります。ODP.NETの非同期APIは、ADO.NETの標準の非同期APIと同じです。現在サポートされていないのは、OracleBulkCopyWriteToServerAsyncのみです。これを使用すると、経験豊富なADO.NET開発者がODP.NET非同期アプリケーションをより簡単に開発できます。

Oracleは、非同期機能をODP.NET固有のクラス(OracleBlob、OracleClob、OracleBFile、OracleXmlStreamなど)にまで拡張しています。それらには、データの読取り、コピーおよび書込みのための非同期メソッドがあります。これは、これらのデータ型のサイズがそれらの処理のI/Oバウンドで非常に大きくなるためです、

非同期ODP.NETサンプル・コード:

static async void Main(){
	OracleConnection oc = new OracleConnection(connectionString);

	// Establish a connection, asynchronously
	Task task = oc.OpenAsync(CancellationToken.None);

	// Execute operation(s) that do not require the connection
	Console.WriteLine(“Hello World”);
	OracleCommand cmd = oc.CreateCommand();
	cmd.CommandText = " select * from employees";

	// "await" OpenAsync completion before executing operations needing connection
	await task;

	// Execute the command
	OracleDataReader = await cmd.ExecuteReaderAsync();
}

ODP.NETの非同期メソッド

OracleConnection:

public Task OpenAsync()

public override Task OpenAsync(CancellationToken cancellationToken)

public Task OpenWithNewPasswordAsync(string newPassword)

public Task OpenWithNewPasswordAsync(string newPassword, CancellationToken cancellationToken)

public Task OpenWithNewPasswordAsync(SecureString secureNewPassword)

public Task OpenWithNewPasswordAsync(SecureString secureNewPassword, CancellationToken cancellationToken)

OracleCommand:

public Task<int> ExecuteNonQueryAsync()

public override Task<int> ExecuteNonQueryAsync(CancellationToken cancellationToken)

public Task<OracleDataReader> ExecuteReaderAsync()

public Task<OracleDataReader> ExecuteReaderAsync(CancellationToken cancellationToken);

public Task<OracleDataReader> ExecuteReaderAsync(CommandBehavior behavior)

public Task<OracleDataReader> ExecuteReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)

public Task<object> ExecuteScalarAsync()

public override Task<object> ExecuteScalarAsync(CancellationToken cancellationToken)

public Task<XmlReader> ExecuteXmlReaderAsync()

public Task<XmlReader> ExecuteXmlReaderAsync(CancellationToken cancellationToken)

OracleDataReader:

public Task<bool> IsDBNullAsync(int i)

public override Task<bool> IsDBNullAsync(int i, CancellationToken cancellationToken)

public Task<T> GetFieldValueAsync<T>(int i)

public override Task<T> GetFieldValueAsync<T>(int i, CancellationToken cancelToken)

public Task<bool> NextResultAsync()

public override Task<bool> NextResultAsync(CancellationToken cancellationToken)

public Task<bool> ReadAsync()

public override Task<bool> ReadAsync(CancellationToken cancellationToken)

OracleDataSource:

public ValueTask<DbConnection> OpenConnectionAsync(CancellationToken cancellationToken)

public void DisposeAsync()

OracleBlob:

public Task<Int64> CopyToAsync(OracleBlob obj)

public Task<Int64> CopyToAsync(OracleBlob obj, CancellationToken cancellationToken)

public Task<Int64> CopyToAsync(OracleBlob obj, Int64 dst_offset)

public Task<Int64> CopyToAsync(OracleBlob obj, Int64 dst_offset, CancellationToken cancellationToken)

public Task<Int64> CopyToAsync(Int64 src_offset, OracleBlob obj, Int64 dst_offset, Int64 amount)

public Task<Int64> CopyToAsync(Int64 src_offset, OracleBlob obj, Int64 dst_offset, Int64 amount, CancellationToken cancellationToken)

public Task<int> ReadAsync(byte[] buffer, int offset, int count)

public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)

public Task<int> WriteAsync(byte[] buffer, int offset, int count)

public override Task<int> WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)

OracleClob:

public Task<Int64> CopyToAsync(OracleClob obj)

public Task<Int64> CopyToAsync(OracleClob obj, CancellationToken cancellationToken)

public Task<Int64> CopyToAsync(OracleClob obj, Int64 dst_offset)

public Task<Int64> CopyToAsync(OracleClob obj, Int64 dst_offset, CancellationToken cancellationToken)

public Task<Int64> CopyToAsync(Int64 src_offset, OracleClob obj, Int64 dst_offset, Int64 amount)

public Task<Int64> CopyToAsync(Int64 src_offset, OracleClob obj, Int64 dst_offset, Int64 amount, CancellationToken cancellationToken)

public Task<int> ReadAsync(byte[] buffer, int offset, int count)

public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)

public Task<int> ReadAsync(char[] buffer, int offset, int count)

public Task<int> ReadAsync(char[] buffer, int offset, int count, CancellationToken cancellationToken)

public Task<int> WriteAsync(byte[] buffer, int offset, int count)

public override Task<int> WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)

public Task<int> WriteAsync(char[] buffer, int offset, int count)

public Task<int> WriteAsync(char[] buffer, int offset, int count, CancellationToken cancellationToken)

OracleBFile:

public Task<Int64> CopyToAsync(OracleBlob obj)

public Task<Int64> CopyToAsync(OracleBlob obj, CancellationToken cancellationToken)

public Task<Int64> CopyToAsync(OracleBlob obj, Int64 dst_offset)

public Task<Int64> CopyToAsync(OracleBlob obj, Int64 dst_offset, CancellationToken cancellationToken)

public Task<Int64> CopyToAsync(Int64 src_offset, OracleBlob obj, Int64 dst_offset, Int64 amount)

public Task<Int64> CopyToAsync(Int64 src_offset, OracleBlob obj, Int64 dst_offset, Int64 amount, CancellationToken cancellationToken)

public Task<Int64> CopyToAsync(OracleClob obj)

public Task<Int64> CopyToAsync(OracleClob obj, CancellationToken cancellationToken)

public Task<Int64> CopyToAsync(OracleClob obj, Int64 dst_offset)

public Task<Int64> CopyToAsync(OracleClob obj, Int64 dst_offset, CancellationToken cancellationToken)

public Task<Int64> CopyToAsync(Int64 src_offset, OracleClob obj, Int64 dst_offset, Int64 amount)

public Task<Int64> CopyToAsync(Int64 src_offset, OracleClob obj, Int64 dst_offset, Int64 amount, CancellationToken cancellationToken)

public Task<int> ReadAsync(byte[] buffer, int offset, int count)

public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)

OracleXmlStream:

public Task<int> ReadAsync(byte[] buffer, int offset, int count)

public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)

public Task<int> ReadAsync(char[] buffer, int offset, int count)

public Task<int> ReadAsync(char[] buffer, int offset, int count, CancellationToken cancellationToken)

データベース・パイプライン処理

非同期ODP.NETコールでは、データベースに対してリクエストが送信され、クライアント側でのレスポンスが非同期的に読み取られます。データベース・サーバーでは、処理はクライアントから独立しておりデフォルトでは同期されます。

Oracle Database 23aiの新機能であるデータベース・パイプライン処理では、データベース・サーバー側での非同期実行機能が提供されます。後続の処理では、以前の処理のレスポンスを受信する前でもリクエストを送信できます。ODP.NETは、引き続き、それぞれのコマンドがデータベースに発行されたのと同じ順序で結果を受信します。

実行時の主な利点は、スループットとパフォーマンスの向上です。データベースは、新しいODP.NETコマンドが到着すると、前のコマンドが完了するまでブロックするのではなく、受信できます。

パイプライン処理では、ODP.NETの非同期APIを使用する必要があります。アプリケーションで、実質的にコードを変更する必要なく、非同期ODP.NETアプリケーションにパイプライン処理を追加できます。パイプライン処理はデータベース・サーバー機能であるため、パイプライン処理が有効になっている場合でも無効になっている場合でも、機能的には、クライアント・アプリケーションにとって違いはありません。

管理対象ODP.NETおよびODP.NET Coreでは、データベース・パイプライン処理がサポートされています。デフォルトでは、この機能は無効になっています。これを有効にするには、OracleConfigurationPipeliningプロパティをtrueに設定します。または、管理対象ODP.NETの場合は、.NET構成ファイルのPipelining設定をtrueに変更します。Oracle Database 23ai以降を使用すること以外に、データベース設定は必要ありません。

ODP.NETのパイプライン処理のサンプル・コード:

static async void Main(){ 

	//Enable Pipelining
	OracleConfiguration.Pipelining = true;

	OracleConnection oc = new OracleConnection(connectionString);
	await oc.OpenAsync(CancellationToken.None);

	OracleCommand cmd = oc.CreateCommand();
	OracleCommand cmd2 = oc.CreateCommand();

	cmd.CommandText = "update table1 set col1 = 1 where col2 = 2";
	cmd2.CommandText = "update table2 set col3 = 3 where col4 = 4";

	//Execute commands asynchronously with pipelining
	Task<int> task = cmd.ExecuteNonQueryAsync(CancellationToken.None);
	Task<int> task2 = cmd2.ExecuteNonQueryAsync(CancellationToken.None);

	//Execute other operations that do not require query results
	Console.WriteLine(“Hello World”);

	//Await the asynchronous tasks to complete
	int updatedRows = await task;
	int updatedRows2 = await task2;

	Console.WriteLine(“Number updated rows are ” + updatedRows + “ and ” + updatedRows2);
}

パイプライン処理が有効になっている場合、OracleCommandCancelメソッド・コール、コマンド・タイムアウトおよびCancellationTokenによる非同期ODP.NETコマンドの取消しはサポートされていません。パイプライン処理が無効になっている場合、非同期ODP.NETコマンドの取消しおよびタイムアウトはサポートされており同期ODP.NETコマンドの取消しおよびタイムアウトと同じ動作になります。