C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

MemoryStreamのメモリ解放

[トピック内 7 記事 (1 - 7 表示)]  << 0 >>

■89221 / inTopicNo.1)  MemoryStreamのメモリ解放
  
□投稿者/ 小道 (1回)-(2018/11/08(Thu) 19:24:08)

分類:[C#] 

下記コードにて、重いJPG(30MB)をループで数十枚処理しようとするとメモリ不足が出てしまいます。
5枚ぐらいは正常処理されます。ご助力願います。
この関数はJPGイメージに任意の緯度経度のEXIF情報を追加するものです。
ms.Dispose();をreturnの前に追加すると呼び出し側でエラーになってしまいます。

どのようにすれば軽くなりますでしょうか?
    	


static Image Geotag(Image original, double lat, double lng)
		{
    	// These constants come from the CIPA DC-008 standard for EXIF 2.3
    	const short ExifTypeByte = 1;
    	const short ExifTypeAscii = 2;
    	const short ExifTypeRational = 5;

    	const int ExifTagGPSVersionID = 0x0000;
    	const int ExifTagGPSLatitudeRef = 0x0001;
    	const int ExifTagGPSLatitude = 0x0002;
    	const int ExifTagGPSLongitudeRef = 0x0003;
    	const int ExifTagGPSLongitude = 0x0004;

    	char latHemisphere = 'N';
    	if (lat < 0)
    	{
        latHemisphere = 'S';
        lat = -lat;
        
    	}
    	char lngHemisphere = 'E';
    	if (lng < 0)
    	{
        lngHemisphere = 'W';
        lng = -lng;
       
    	}

    	MemoryStream ms = new MemoryStream();
    	original.Save(ms, ImageFormat.Jpeg);
    	ms.Seek(0, SeekOrigin.Begin);
	Image img = Image.FromStream(ms);
   	 		
    	AddProperty(img, ExifTagGPSVersionID, ExifTypeByte, new byte[] { 2, 3, 0, 0 });
    	AddProperty(img, ExifTagGPSLatitudeRef, ExifTypeAscii, new byte[] { (byte)latHemisphere, 0 });
    	AddProperty(img, ExifTagGPSLatitude, ExifTypeRational, ConvertToRationalTriplet(lat));
    	AddProperty(img, ExifTagGPSLongitudeRef, ExifTypeAscii, new byte[] { (byte)lngHemisphere, 0 });
    	AddProperty(img, ExifTagGPSLongitude, ExifTypeRational, ConvertToRationalTriplet(lng));
  		
     	return img;

		}

引用返信 編集キー/
■89222 / inTopicNo.2)  Re[1]: MemoryStreamのメモリ解放
□投稿者/ Hongliang (719回)-(2018/11/08(Thu) 19:34:34)
返しているImageをDisposeしない限りはどうにもならないです。
MemoryStreamはDisposeしてもメモリには影響しません。

ところで、コピーを作ってそれにPropertyItemを追加しているようですが、コピーではなくてオリジナルに直接追加してはいけないのでしょうか。
引用返信 編集キー/
■89225 / inTopicNo.3)  Re[2]: MemoryStreamのメモリ解放
□投稿者/ 小道 (2回)-(2018/11/09(Fri) 08:47:13)
No89222 (Hongliang さん) に返信
> 返しているImageをDisposeしない限りはどうにもならないです。
> MemoryStreamはDisposeしてもメモリには影響しません。
> 
> ところで、コピーを作ってそれにPropertyItemを追加しているようですが、コピーではなくてオリジナルに直接追加してはいけないのでしょうか。

Hongliangさん、ご回答ありがとうございます。
PropetyItemは元々別のクラスから持ってきたため、そのままになっています。
呼び出し側は以下のようなコードなのですが、t.Dispose();しても軽くならず悩んでおります。



		void Button1Click(object sender, EventArgs e)
		{
		//フォルダーパスを格納	
				
		// FolderBrowserDialog の新しいインスタンスを生成する (デザイナから追加している場合は必要ない)
    		FolderBrowserDialog folderBrowserDialog1 = new FolderBrowserDialog();

    		// ダイアログの説明を設定する
    		folderBrowserDialog1.Description = "ジオタグしたい写真が格納してあるフォルダを選んでください。";

    		// ルートになる特殊フォルダを設定する (初期値 SpecialFolder.Desktop)
    		folderBrowserDialog1.RootFolder = System.Environment.SpecialFolder.MyComputer;

    		// 初期選択するパスを設定する
    		folderBrowserDialog1.SelectedPath = @"C:\";

    		// [新しいフォルダ] ボタンを表示する (初期値 true)
    		//folderBrowserDialog1.ShowNewFolderButton = true;

    		// ダイアログを表示し、戻り値が [OK] の場合は、選択したディレクトリを表示する
    		if (folderBrowserDialog1.ShowDialog() == DialogResult.OK) {
    		folderpass = folderBrowserDialog1.SelectedPath;
    		
    		}

    		// 不要になった時点で破棄する (正しくは オブジェクトの破棄を保証する を参照)
    		folderBrowserDialog1.Dispose();
    	
    		IEnumerable<string> files =
    		System.IO.Directory.EnumerateFiles(folderpass, "*", System.IO.SearchOption.AllDirectories);
				
			string s1 = Interaction.InputBox("緯度を10進法で入力してください");
  			listBox2.Items.Add(this.Text = s1);
    		string s2 = Interaction.InputBox("経度を10進法で入力してください");
  			listBox3.Items.Add(this.Text = s2);
  		
    		string passout = @"C:\geotag\";
    		this.Text = "geotagimege";
    	
    		if (Directory.Exists(passout))
       	 	{
            
        	}
        	else
        	{
        	Directory.CreateDirectory(passout);
        	}
    	
    	
    	int count = 0;
  		int countoutput;
  		
  		double ido = double.Parse(s1);
  		double keido = double.Parse(s2);
		
  		//ファイル数を取得
  		var directory = folderpass;
		int fileCount = Directory.GetFiles(directory, "*", SearchOption.TopDirectoryOnly).Length;
  		  		
  		foreach(string s in files)
  		{		
  			string filepass = Path.GetFileName(s);
  			
  			string passin = @"C:\geotag\" + s;
  			
			Bitmap t = new Bitmap(s);

			
  				  	if(count == fileCount)
  					{
  					goto Exit;
  					}
  				  	
  				    else
  					{			
  					countoutput = count + 1;
  					
  					Geotag(t, ido, keido)
    				.Save(@"C:\geotag\" + filepass + "output" +  countoutput + ".jpg", ImageFormat.Jpeg);
  					
  					count = count + 1;
 					t.Dispose();
 					
  					
  					}
  						
  		}
 
		Exit:
  		MessageBox.Show("すべての写真のジオタグが完了しました。");
  		Application.Exit();
    	
    	
		}
	}

引用返信 編集キー/
■89226 / inTopicNo.4)  Re[3]: MemoryStreamのメモリ解放
□投稿者/ にゃるら (49回)-(2018/11/09(Fri) 09:26:17)
No89225 (小道 さん) に返信

Geotag(t, ido, keido).Save(...)

を

var saveImage = Geotag(t, ido, keido);
saveImage.Save(...);
if (saveImage != t)
{
    saveImage.Dipsose();
}

ですかね。

引用返信 編集キー/
■89229 / inTopicNo.5)  Re[3]: MemoryStreamのメモリ解放
□投稿者/ 魔界の仮面弁士 (1926回)-(2018/11/09(Fri) 10:25:11)
No89225 (小道 さん) に返信
> Geotag(t, ido, keido)
> .Save(@"C:\geotag\" + filepass + "output" + countoutput + ".jpg", ImageFormat.Jpeg);

戻り値を解放し忘れています。Hongliang さんからも
『返しているImageをDisposeしない限りはどうにもならないです』と指摘されていますよね。

 using (var img = Geotag(t, ido, keido))
 {
  img.Save(@"C:\geotag\" + filepass + "output" + countoutput + ".jpg", ImageFormat.Jpeg);
 }


ついでに、他の部分についても。


> // 不要になった時点で破棄する (正しくは オブジェクトの破棄を保証する を参照)
> folderBrowserDialog1.Dispose();
FolderBrowserDialog は IDisposable ですが、これの Dispose は必須ではないので、
この行の有無は特に影響しないはず…。


> // ダイアログを表示し、戻り値が [OK] の場合は、選択したディレクトリを表示する
> if (folderBrowserDialog1.ShowDialog() == DialogResult.OK) {
>   folderpass = folderBrowserDialog1.SelectedPath;
> }
folderPath ではなく
folderpass な点に違和感が。(Pass:通り過ぎる、Path:経路,小道)

ところでキャンセルされた場合も、そのまま処理を継続して良いのですか?


> IEnumerable<string> files =
> System.IO.Directory.EnumerateFiles(folderpass, "*", System.IO.SearchOption.AllDirectories);
AllDirectories での列挙中に、アクセス権の無いパスに当たってしまった場合、
列挙処理が中断されてしまうのでご注意ください。
https://qiita.com/otagaisama-1/items/8e5022367ee13a0a0193


> string s1 = Interaction.InputBox("緯度を10進法で入力してください");
名前からすると、Microsoft.VisualBasic.Interaction.InputBox メソッドでしょうか。
(あるいは、10進数以外での入力を許容しない、独自のダイアログを表示している?)


> string passout = @"C:\geotag\";
> this.Text = "geotagimege";
> if (Directory.Exists(passout))
> {
> }
> else
> {
>   Directory.CreateDirectory(passout);
> }

少し補足しておくと、Directory.CreateDirectory メソッドは、
C:\geotag ディレクトリが存在していてもエラーにはならない仕様ですが、
C:\geotag ファイルが存在していた場合はエラーになるという点が、
見落とされがちです。ファイル操作である以上、例外処理は含めておいた方が安全です。

ただし高速化という点では、事前の Directory.Exists チェックも有効な手段と言えます。
(今回は連続した呼び出しを行っているわけでも無いので、さほど重要では無いですが)
http://mag.autumn.org/Content.modf?id=20050327144238



> double ido = double.Parse(s1);
> double keido = double.Parse(s2);
InputBox 値の検査のため、double.TryParse の利用をお奨めします。


> IEnumerable<string> files =
> System.IO.Directory.EnumerateFiles(folderpass, "*", System.IO.SearchOption.AllDirectories);
> var directory = folderpass;
> int fileCount = Directory.GetFiles(directory, "*", SearchOption.TopDirectoryOnly).Length;

このあたりのコードに、ちぐはぐな継ぎ接ぎ感というか、不自然さを感じます。

・folderpass と directory の使い分けの意図は何ですか?
 どちらも同じ場所を示しているようですが…。

・前者は System.IO 名前空間を「明示」して、『EnumerateFiles』を使い、
 後者は System.IO 名前空間を「省略」して、『GetFiles』を使っていますが、
 このあたり、何か意図があるのでしょうか。

・前者は AllDirectories、後者は TopDirectoryOnly の探索になっていますが
 意図的に使い分けているのでしょうか。探索深度が異なるので、
 files.Count() は fileCount 以上の値になるはずですが、
 ループ処理内の goto も含め、意図が読み取れませんでした。


> foreach(string s in files)
> {
>   string filepass = Path.GetFileName(s);
>   string passin = @"C:\geotag\" + s;

この passin は未使用ですが、何のために用意されているのでしょうか?

files には、子階層・孫階層のディレクトリのファイルも含まれるので、
GetFileName からは、別階層にあった同名ファイルが返される可能性があります。
その場合、passin が競合するパスになりえますが…。



> Bitmap t = new Bitmap(s);
なぜ using を使わないのでしょうか?

> if(count == fileCount)
> {
>   goto Exit;
> }
この場合 t.Dispose() の行が呼ばれませんが、本当にそれで良いのですか?
何故 goto が必要なのかも謎です。


> Exit:
>   MessageBox.Show("すべての写真のジオタグが完了しました。");
MessageBoxIcon も指定した方が良いかも。

>   Application.Exit();
FormClosing がキャンセルされた場合などは、Application.Exit() では
終了しないこともありますね。(引数付きの Exit メソッドを使って判定できます)
引用返信 編集キー/
■89230 / inTopicNo.6)  Re[4]: MemoryStreamのメモリ解放
□投稿者/ shu (1153回)-(2018/11/09(Fri) 10:43:58)
No89226 (にゃるら さん) に返信

解放が大変な処理なようでしたら
1ファイル処理する部分を別プロセスAにして
しまい、各ファイル毎にプロセスAを起動処理が
終わったらプロセスAが自身で終了すれば
プロセスに割り当てられたManagedMemoryは解放
されるので大丈夫かもしれないです。
引用返信 編集キー/
■89232 / inTopicNo.7)  Re[5]: MemoryStreamのメモリ解放
□投稿者/ 小道 (3回)-(2018/11/09(Fri) 18:12:37)
皆さん、コメントありがとうございます。
ご指摘どおりに組みなおして、合わせて別プロセスにしたところ、かなり早くなりました。
解決済み
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ