tagCANDY CGI VBレスキュー(花ちゃん) の Visual Basic 2010 用 掲示板(VB.NET 掲示板)
VBレスキュー(花ちゃん) の Visual Basic 2010 用 掲示板(VB.NET 掲示板)
[ツリー表示へ]  [ワード検索]  [Home]

タイトル Re^3: 共有フォルダ上のあるファイルへ複数ユーザーからランダムアクセスすると読みだせなくなる
投稿日: 2019/07/10(Wed) 12:00
投稿者魔界の仮面弁士
> FS.Write(bytData, 0, DataLength) の下に
> FileStream.Flush(Boolean) ってことですか?

失礼しました。 VB2010 といっても、
.NET Framework 4 を使っているのではなく、
.NET Framework 3.5 での開発なのですね。

Flush(Boolean) は .NET Framework 4 で追加されたメソッドです。
3.5 で使えるのは Flush() ですね。これは Flush(False) に相当します。


FileStream は、Read/Write のための内部バッファーを持っていますが、
Write 時にすべての内容が即座にファイルに出力されるとは限らず、
書き込まれずに内部バッファに残留した状態になることがあります。
この残留バッファは、即座に出力されることは無く、
「次回の Write 時」「Close あるいは Flush された時」まで保留されます。

Flush() あるいは Flush(False) を呼び出すと、このバッファが
直ちに出力されてファイルに書き込まれます(Read バッファの Seek 補正も行われます)。

一方 Flush(True) メソッドは、Flush(False) 相当の処理が行われた後で、
さらに続けて FlushFileBuffers API を呼び出すように設計されています。
これは、.NET 側ではなく、OS のライトバッファをフラッシュするための命令です。
(これを呼ばずとも、書き込み後暫くすると自動的にフラッシュされるはずですが、
 負荷状況によってはフラッシュのタイミングが変わることがあります)

もしも .NET 3.5 で Flush(True) 相当の処理を行いたい場合は、
 fs.Flush()
 FlushFileBuffers(fs.Handle)
というコードで処理できるかもしれません。試していませんけど。

http://rarara.cafe.coocan.jp/cgi-bin/lng/vc/vclng.cgi?print+200405/04050054.txt


> Q1)bufferSize は 4096 (デフォルト)で良いものなんでしょうか?

bufferSize は、Read / Write 時の「既定のバッファーサイズ」を表します。

今回は、固定長のランダムアクセスファイルのようなので、
たとえば各レコード長を表す Private DataLength As Long と
同じ値またはその倍数にしておくのはどうでしょうか。


> Q2)ファイルに文字列は問題なく?書き込まれていて読み込み側の問題だと思っていましたが
>  上記コードを書いて確実にデータをHDDに書込む処理を書くとなにか変わるものなのでしょうか?

どうでしょうね。デバイスにもよるところなので、実検証は必要ですが、
少なくとも Flush() は行っておいた方が良いと思います。

コンストラクタで FileOptions.WriteThrough を指定した場合は、
バッファを経由せずにディスクに直接出力させるために、
パフォーマンスを犠牲にして OS のライトキャッシュが無効化されますので、
Flush(True) あるいは FlushFileBuffers の出番はありませんが、
それでも Flush(False) は必要となります。

毎回 Flush するのが面倒なら、StreamWriter の AutoFlush に頼るという手もあります。


> ランダムアクセス?での同時アクセス数に限界があるのかな?と素人推論しています。
> 感覚でいうと10台ぐらいで発生している気がします。

ネットワークや NAS 側の性能に依存する可能性はありそうですね。
10 万円ぐらいの NAS だと、上限 10 人ぐらいが相場でしょう。
https://www.itmedia.co.jp/pcuser/articles/1403/12/news024.html


==== 以下蛇足 ====

> プログラム@(抜粋)
>  Dim bufNow As Date = Now
>  Dim seekNo As Double = CDbl(Format$(bufNow, "HH")) * 3600 _
>                       + CDbl(Format$(bufNow, "mm")) * 60 + CDbl(Format$(bufNow, "ss"))
>  Dim Data As String = Format$(bufNow, "HHmmss") & "-" & 固定文字列 & vbCrLf
>  FS.Seek(DataLength * seekNo, SeekOrigin.Begin)

Seek メソッドの第一引数は Long を指定する必要がありますが、
上記では Double を渡しています。Option Strict On 時にエラーになりますね。

ということで、seekNo は
  Dim seekNo As Double = Int(bufNow.TimeOfDay.TotalSeconds())
あるいは、
  Dim seekNo As Long = CLng(Math.Floor(bufNow.TimeOfDay.TotalSeconds()))
に変更することを提案しておきます。


> 動作Aのプログラム(抜粋)
> Dim LineN As Integer = bufDate.Hour * 3600 + bufDate.Minute * 60 _
>                                     + bufDate.Second   '何行目?
こっちは Integer ですが、値の範囲的には問題無いはず。


それにしても、@とAでコーディングスタイルが随分違いますね。

@
> If today <> Format$(Now, "yyMMdd") Or today = "" Then
A
> If today <> bufDate.ToString("yyMMdd") Or today = "" Then

@は何となく VB6 時代の雰囲気を感じます。
まぁ、Aも OrElse が Or になってたりはしますが…。


> Private Sub CreateNewFile()
大晦日 24 時から正月 0 時に変わるタイミングで呼び出すと、
保存フォルダとファイル名の整合性が崩れる可能性があります。

Timer 処理で行っているように、Now を変数に渡してから使うことをお奨めします。


それと、提示コードだけ見た場合、例外処理が不十分に感じました。
(掲示板投稿のために端折っているせいもあるとは思いますが)

たとえば:
>    If Dir(filePath, FileAttribute.Directory) = "" Then MkDir(filePath)
の場合、filePath なファイルが存在し、かつそれが不可視属性だった場合、
上記の If 文は True となり、その後の MkDir によって IOException がスローされます。

それを受けるための Catch 句は用意されていますが、それによって得られる
lblERR.Text を見ただけでは、どのパスでどの例外クラスが投げられたのかが、
記録されていないので、障害発生時に苦労しそうです。
(エラー内容を画面に表示するかどうかは別として、障害記録は残せた方がベター)

- 関連一覧ツリー をクリックするとツリー全体を一括表示します)

古いスレッドにレスはつけられません。