ファイルへの書き込みに関するベスト プラクティスBest practices for writing to files

重要な APIImportant APIs

開発者がファイル システム I/O 操作を実行するために FileIO および PathIO クラスの Write メソッドを使用するときに、一般的な一連の問題が発生することがあります。Developers sometimes run into a set of common problems when using the Write methods of the FileIO and PathIO classes to perform file system I/O operations. たとえば、一般的な問題には以下のようなものが含まれます。For example, common problems include:

  • ファイルが部分的に書き込まれる。A file is partially written.
  • アプリで、メソッドのいずれかを呼び出すときに例外を受け取る。The app receives an exception when calling one of the methods.
  • 操作で、ターゲット ファイルの名前と似たようなファイル名の .TMP ファイルが残る。The operations leave behind .TMP files with a file name similar to the target file name.

FileIO および PathIO クラスの Write メソッドには、以下のものが含まれます。The Write methods of the FileIO and PathIO classes include the following:

  • WriteBufferAsyncWriteBufferAsync
  • WriteBytesAsyncWriteBytesAsync
  • WriteLinesAsyncWriteLinesAsync
  • WriteTextAsyncWriteTextAsync

この記事では、開発者がこれらのメソッドをいつどのように使用するかをよりよく理解できるように、それらの動作について詳しく説明します。This article provides details about how these methods work so developers understand better when and how to use them. この記事はガイドラインを提供するものであり、考えられるすべてのファイル I/O に関する問題の解決策の提供を試みるものではありません。This article provides guidelines and does not attempt to provide a solution for all possible file I/O problems.

注意

 この記事では、FileIO メソッドを例とし、重点的に説明します。 This article focuses on the FileIO methods in examples and discussions. しかし、PathIO メソッドでは同様のパターンに従い、この記事のガイダンスのほとんどがそれらにも適用されます。However, the PathIO methods follow a similar pattern and most of the guidance in this article applies to those methods too.

利便性と制御Convenience vs. control

StorageFile オブジェクトは、ネイティブの Win32 プログラミング モデルのようなファイル ハンドルではありません。A StorageFile object is not a file handle like the native Win32 programming model. むしろ、StorageFile は、コンテンツを操作するためのメソッドを含むファイルを表したものです。Instead, a StorageFile is a representation of a file with methods to manipulate its contents.

この概念を理解することは、StorageFile で I/O を実行する場合に役立ちます。Understanding this concept is useful when performing I/O with a StorageFile. たとえば、「ファイルへの書き込み」セクションでは、ファイルへの 3 とおりの書き込み方法が示されています。For example, the Writing to a file section presents three ways to write to a file:

  • FileIO.WriteTextAsync メソッドを使用する。Using the FileIO.WriteTextAsync method.
  • バッファーを作成してから、FileIO.WriteBufferAsync メソッドを呼び出す。By creating a buffer and then calling the FileIO.WriteBufferAsync method.
  • ストリームを使用する 4 ステップのモデル:The four-step model using a stream:
    1. ファイルを開き、ストリームを取得します。Open the file to get a stream.
    2. 出力ストリームを取得します。Get an output stream.
    3. DataWriter オブジェクトを作成し、対応する Write メソッドを呼び出します。Create a DataWriter object and call the corresponding Write method.
    4. データ ライターでデータをコミットし、出力ストリームをフラッシュします。Commit the data in the data writer and flush the output stream.

最初の 2 つのシナリオは、アプリで最もよく使用されるものです。The first two scenarios are the ones most commonly used by apps. 単一操作でのファイルへの書き込みは、コーディングして保守するより簡単です。また、アプリでファイル I/O の多くの複雑さに対処しなくても済むようになります。Writing to the file in a single operation is easier to code and maintain, and it also removes the responsibility of the app from dealing with many of the complexities of file I/O. しかし、この利便性の代償として、操作全体の制御と、特定の時点のエラーのキャッチができなくなります。However, this convenience comes at a cost: the loss of control over the entire operation and the ability to catch errors at specific points.

トランザクション モデルThe transactional model

FileIO および PathIO クラスの Write メソッドでは、レイヤーが追加され、上記の 3 番目の書き込みモデルの手順がラップされます。The Write methods of the FileIO and PathIO classes wrap the steps on the third write model described above, with an added layer. このレイヤーは、ストレージ トランザクションでカプセル化されます。This layer is encapsulated in a storage transaction.

データの書き込み中に問題が発生した場合に元のファイルの整合性を保護するために、Write メソッドでは OpenTransactedWriteAsync を使ってファイルを開き、トランザクション モデルを使用します。To protect the integrity of the original file in case something goes wrong while writing the data, the Write methods use a transactional model by opening the file using OpenTransactedWriteAsync. このプロセスで StorageStreamTransaction オブジェクトが作成されます。This process creates a StorageStreamTransaction object. このトランザクション オブジェクトが作成された後、API では、ファイル アクセス サンプル、または StorageStreamTransaction に関する記事のコード例と同じような方法に従って、データを書き込みます。After this transaction object is created, the APIs write the data following a similar fashion to the File Access sample or the code example in the StorageStreamTransaction article.

次の図では、成功した書き込み操作のWriteTextAsync メソッドで実行された基になるタスクを示しています。The following diagram illustrates the underlying tasks performed by the the WriteTextAsync method in a successful write operation. この図では、操作の簡易ビューを提供しています。This illustration provides a simplified view of the operation. たとえば、さまざまなスレッドでテキストのエンコードや非同期完了などの手順は省略されています。For example, it skips steps such as text encoding and async completion on different threads.

ファイルへの書き込みに関する UWP API 呼び出しのシーケンス図

ストリームを使用する、より複雑な 4 ステップ モデルではなく、FileIO および PathIO クラスの Write メソッドを使用する利点は、次のとおりです。The advantages of using the Write methods of the FileIO and PathIO classes instead of the more complex four-step model using a stream are:

  • エラーを含む、すべての中間ステップを処理する 1 回の API 呼び出し。One API call to handle all the intermediate steps, including errors.
  • 問題が発生した場合に、元のファイルが保持される。The original file is kept if something goes wrong.
  • システム状態が可能な限りクリーンな状態に保たれるようにする。The system state will try to be kept as clean as possible.

しかし、考えられる中間単一障害点が非常に多く存在するため、障害の可能性が高くなります。However, with so many possible intermediate points of failure, there’s an increased chance of failure. エラーが発生したときに、プロセスが失敗した場所を把握するのが難しくなる場合があります。When an error occurs it may be difficult to understand where the process failed. 次のセクションでは、Write メソッドを使用するときに発生する可能性のある障害をいくつか示し、考えられる解決策を提供します。The following sections present some of the failures you might encounter when using the Write methods and provide possible solutions.

FileIO および PathIO クラスの Write メソッドの一般的なエラー コードCommon error codes for Write methods of the FileIO and PathIO classes

この表には、アプリ開発者が Write メソッドを使用するときに発生する一般的なエラー コードが示されています。This table presents common error codes that app developers encounter when using the Write methods. 表の手順は、前の図の手順に対応しています。The steps in the table correspond to steps in the previous diagram.

エラー名 (値)Error name (value) 手順Steps 原因Causes 解決策Solutions
ERROR_ACCESS_DENIED (0X80070005)ERROR_ACCESS_DENIED (0X80070005) 55 場合によっては前の操作から、削除する対象として元のファイルにマークが付けられる可能性があります。The original file might be marked for deletion, possibly from a previous operation. 操作を再試行します。Retry the operation.
ファイルへのアクセスが同期されていることを確認します。Ensure access to the file is synchronized.
ERROR_SHARING_VIOLATION (0x80070020)ERROR_SHARING_VIOLATION (0x80070020) 55 元のファイルが別の排他的書き込みによって開かれています。The original file is opened by another exclusive write. 操作を再試行します。Retry the operation.
ファイルへのアクセスが同期されていることを確認します。Ensure access to the file is synchronized.
ERROR_UNABLE_TO_REMOVE_REPLACED (0x80070497)ERROR_UNABLE_TO_REMOVE_REPLACED (0x80070497) 19 から 2019-20 元のファイル (file.txt) が使用中のため、置換できませんでした。The original file (file.txt) could not be replaced because it is in use. 置換の前に、別のプロセスまたは操作でファイルにアクセスされました。Another process or operation gained access to the file before it could be replaced. 操作を再試行します。Retry the operation.
ファイルへのアクセスが同期されていることを確認します。Ensure access to the file is synchronized.
ERROR_DISK_FULL (0x80070070)ERROR_DISK_FULL (0x80070070) 7、14、16、207, 14, 16, 20 トランザクション処理されたモデルでは余分なファイルが作成され、これにより、余分なストレージが消費されています。The transacted model creates an extra file, and this consumes extra storage.
ERROR_OUTOFMEMORY (0x8007000E)ERROR_OUTOFMEMORY (0x8007000E) 14、1614, 16 これは、複数の未処理の I/O 操作または大きなファイル サイズが原因で発生する場合があります。This can happen due to multiple outstanding I/O operations or large file sizes. ストリームを制御することによる、より詳細な方法でエラーが解決される可能性があります。A more granular approach by controlling the stream might resolve the error.
E_FAIL (0x80004005)E_FAIL (0x80004005) 任意Any その他Miscellaneous 操作を再試行します。Retry the operation. それでも失敗する場合は、プラットフォームのエラーである可能性があります。アプリが不整合な状態であるため、終了する必要があります。If it still fails, it might be a platform error and the app should terminate because it's in an inconsistent state.

エラーにつながる可能性のあるファイルの状態に関するその他の考慮事項Other considerations for file states that might lead to errors

Write メソッドによって返されたエラーとは別に、ここでは、ファイルへの書き込み時にアプリで予期される内容に関するガイダンスをいくつか示します。Apart from errors returned by the Write methods, here are some guidelines on what an app can expect when writing to a file.

操作が完了した場合にのみ、データがファイルに書き込まれるData was written to the file if and only if operation completed

アプリでは、書き込み操作の進行中にファイル内のデータについて何も想定しません。Your app should not make any assumption about data in the file while a write operation is in progress. 操作が完了する前にファイルにアクセスしようとすると、整合性のないデータになる可能性があります。Trying to access the file before an operation completes might lead to inconsistent data. アプリでは、未処理の I/O の追跡を担当する必要があります。Your app should be responsible of tracking outstanding I/Os.

ReadersReaders

書き込まれているファイルが正当な閲覧者によって使用もされている (つまり、FileAccessMode.Read で開かれている) 場合、以降の読み取りは ERROR_OPLOCK_HANDLE_CLOSED (0x80070323) エラーで失敗します。If the file that being written to is also being used by a polite reader (that is, opened with FileAccessMode.Read, subsequent reads will fail with an error ERROR_OPLOCK_HANDLE_CLOSED (0x80070323). アプリでは、書き込み操作の進行中に、再度読み取るファイルを開くために再試行される場合があります。Sometimes apps retry opening the file for read again while the Write operation is ongoing. これにより、競合状態となる可能性があり、元のファイルを置換できないため、そのファイルの上書きが試行されると、書き込みは最終的に失敗します。This might result in a race condition on which the Write ultimately fails when trying to overwrite the original file because it cannot be replaced.

KnownFolders からのファイルFiles from KnownFolders

自分のアプリが、KnownFolders のいずれかに存在するファイルにアクセスしようとしている唯一のアプリではない場合があります。Your app might not be the only app that is trying to access a file that resides on any of the KnownFolders. 操作が成功しても、アプリでファイルに書き込まれた内容が、ファイルの読み取りの次回の試行時に一定のままである保証はありません。There’s no guarantee that if the operation is successful, the contents an app wrote to the file will remain constant the next time it tries to read the file. また、このシナリオでは、共有またはアクセス拒否エラーがより一般的になります。Also, sharing or access denied errors become more common under this scenario.

競合する I/OConflicting I/O

アプリでローカル データのファイルに対して Write メソッドを使用すると、同時実行エラーの可能性は低くなる場合がありますが、それでもいくつか注意が必要です。The chances of concurrency errors can be lowered if our app uses the Write methods for files in its local data, but some caution is still required. 複数の書き込み操作が同時にファイルに送信されている場合、ファイルのデータの最終的な状態については保証されません。If multiple Write operations are being sent concurrently to the file, there’s no guarantee about what data ends up in the file. これを軽減するために、アプリでファイルへの書き込み操作をシリアル化することをお勧めします。To mitigate this, we recommend that your app serializes Write operations to the file.

~TMP ファイル~TMP files

場合によっては、操作が強制的に中断されると (たとえば、アプリが OS によって中断または終了された場合)、トランザクションがコミットされないか、適切に閉じられません。Occasionally, if the operation is forcefully cancelled (for example, if the app was suspended or terminated by the OS), the transaction is not committed or closed appropriately. これにより、(.~TMP) 拡張子が付いたファイルが残る可能性があります。This can leave behind files with a (.~TMP) extension. アプリのアクティブ化を処理するときに、(アプリのローカル データに存在する場合は) これらの一時ファイルの削除を検討してください。Consider deleting these temporary files (if they exist in the app's local data) when handling the app activation.

ファイルの種類に基づく考慮事項Considerations based on file types

ファイルの種類、アクセス頻度、およびファイルのサイズに応じて、一部のエラーがより一般的になる場合があります。Some errors can become more prevalent depending on the type of files, the frequency on which they’re accessed, and their file size. 一般に、アプリからアクセスできるファイルには 3 つのカテゴリがあります。Generally, there are three categories of files your app can access:

  • アプリのローカル データ フォルダーで、ユーザーによって作成および編集されたファイル。Files created and edited by the user in your app's local data folder. これらはアプリの使用時にのみ、作成および編集され、アプリ内にのみ存在します。These are created and edited only while using your app, and they exist only within the app.
  • アプリのメタデータ。App metadata. アプリではこれらのファイルを使用して、自身の状態を追跡します。Your app uses these files to keep track of its own state.
  • アプリでアクセスする機能が宣言された、ファイル システムの場所にあるその他のファイル。Other files in locations of the file system where your app has declared capabilities to access. これらは、KnownFolders のいずれかに最も一般的に配置されます。These are most commonly located in one of the KnownFolders.

アプリでは最初の 2 つのカテゴリのファイルを完全に制御できます。これらは、アプリのパッケージ ファイルの一部であり、アプリのみでアクセスされるためです。Your app has full control on the first two categories of files, because they’re part of your app's package files and are accessed by your app exclusively. 最後のカテゴリのファイルの場合、アプリでは、他のアプリと OS サービスによってファイルに同時にアクセスされる可能性があることに注意する必要があります。For files in the last category, your app must be aware that other apps and OS services may be accessing the files concurrently.

アプリに応じて、ファイルへのアクセスは頻度が異なる場合があります。Depending on the app, access to the files can vary on frequency:

  • 非常に低い。Very low. 通常、これらはアプリが起動したときに一度開かれ、アプリが中断されたときに保存されるファイルです。These are usually files that are opened once when the app launches and are saved when the app is suspended.
  • 低い。Low. これらは、特にユーザーによって (保存や読み込みなど) 操作されるファイルです。These are files that the user is specifically taking an action on (such as save or load).
  • 中程度または高い。Medium or high. これらは、アプリで常にデータを更新する (自動保存機能や定期的なメタデータ追跡など) 必要があるファイルです。These are files in which the app must constantly update data (for example, autosave features or constant metadata tracking).

ファイルのサイズについては、WriteBytesAsync メソッドに関する以下のグラフのパフォーマンス データを見ていきます。For file size, consider the performance data in the following chart for the WriteBytesAsync method. このグラフでは、制御された環境でのファイル サイズごとの 10000 操作の平均パフォーマンスを超える、ファイル サイズと操作を完了するまでの時間を比較します。This chart compares the time to complete an operation vs file size, over an average performance of 10000 operations per file size in a controlled environment.

WriteBytesAsync のパフォーマンス

ハードウェアおよび構成によって得られる絶対時刻値が異なるため、このグラフの y 軸の時間値は意図的に省略されています。The time values on the y-axis are omitted intentionally from this chart because different hardware and configurations will yield different absolute time values. しかし、マイクロソフトによるテストではこのような傾向を一貫して監視しています。However, we have consistently observed these trends in our tests:

  • 非常に小さなファイル (1 MB 以下) の場合:操作が完了するまでの時間は、一貫して短くなります。For very small files (<= 1 MB): The time to complete the operations is consistently fast.
  • 大きなファイル (1 MB を超える) の場合:操作が完了するまでの時間が、指数関数的に増え始めます。For larger files (> 1 MB): The time to complete the operations starts to increase exponentially.

アプリの中断時の I/OI/O during app suspension

以降のセッションで使用する状態情報またはメタデータを保持する必要がある場合は、中断を処理するようにアプリを設計する必要があります。Your app must designed to handle suspension if you want to keep state information or metadata for use in later sessions. アプリの中断に関する背景情報については、アプリのライフサイクルに関するページとこちらのブログ記事を参照してください。For background information about app suspension, see App lifecycle and this blog post.

OS でアプリに延長実行が許可されない限り、アプリが中断された場合に、そのすべてのリソースが解放され、そのデータが保存されるまで 5 秒かかります。Unless the OS grants extended execution to your app, when your app is suspended it has 5 seconds to release all its resources and save its data. 最高の信頼性とユーザー エクスペリエンスのために、中断タスクを処理する必要がある時間が限られていることを常に前提とします。For the best reliability and user experience, always assume the time you have to handle suspension tasks is limited. 中断タスクを処理する 5 秒間は、以下のガイドラインに注意してください。Keep in mind the following guidelines during the 5 second time period for handling suspension tasks:

  • フラッシュおよびリリース操作によって発生する競合状態を回避するために、I/O を最小限に抑えるようにしてください。Try to keep I/O to a minimum to avoid race conditions caused by flushing and release operations.
  • 書き込みに数百ミリ秒以上の時間を要するファイルの書き込みは避けてください。Avoid writing files that require hundreds of milliseconds or more to write.
  • アプリで Write メソッドが使用されている場合は、これらのメソッドで必要なすべての中間ステップに注意してください。If your app uses the Write methods, keep in mind all the intermediate steps that these methods require.

中断時にアプリで少量の状態データを操作するのであれば、ほとんどの場合、Write メソッドを使用して、データをフラッシュすることができます。If your app operates on a small amount of state data during suspension, in most cases you can use the Write methods to flush the data. しかし、アプリで大量の状態データを使用する場合は、ストリームを使ってデータを直接格納することを検討してください。However, if your app uses a large amount of state data, consider using streams to directly store your data. これは、Write メソッドのトランザクション モデルによって発生する遅延を減らすのに役立つ場合があります。This can help reduce the delay introduced by the transactional model of the Write methods.

例については、BasicSuspension サンプルに関するページを参照してください。For an example, see the BasicSuspension sample.

その他の例とリソースOther examples and resources

特定のシナリオでのいくつかの例と他のリソースを以下に示します。Here are several examples and other resources for specific scenarios.

ファイル I/O の例を再試行するためのコード例Code example for retrying file I/O example

ユーザーが保存するファイルを選んだ後に書き込みが行われることを前提とし、書き込みを再試行する方法の疑似コード例 (C#) を以下に示します。The following is a pseudo-code example on how to retry a write (C#), assuming the write is to be done after the user picks a file for saving:

Windows.Storage.Pickers.FileSavePicker savePicker = new Windows.Storage.Pickers.FileSavePicker();
savePicker.FileTypeChoices.Add("Plain Text", new List<string>() { ".txt" });
Windows.Storage.StorageFile file = await savePicker.PickSaveFileAsync();

Int32 retryAttempts = 5;

const Int32 ERROR_ACCESS_DENIED = unchecked((Int32)0x80070005);
const Int32 ERROR_SHARING_VIOLATION = unchecked((Int32)0x80070020);

if (file != null)
{
    // Application now has read/write access to the picked file.
    while (retryAttempts > 0)
    {
        try
        {
            retryAttempts--;
            await Windows.Storage.FileIO.WriteTextAsync(file, "Text to write to file");
            break;
        }
        catch (Exception ex) when ((ex.HResult == ERROR_ACCESS_DENIED) ||
                                   (ex.HResult == ERROR_SHARING_VIOLATION))
        {
            // This might be recovered by retrying, otherwise let the exception be raised.
            // The app can decide to wait before retrying.
        }
    }
}
else
{
    // The operation was cancelled in the picker dialog.
}

ファイルへのアクセスを同期するSynchronize access to the file

.NET での並列プログラミングに関するブログは、並列プログラミングについてのガイダンスの優れたリソースです。The Parallel Programming with .NET blog is a great resource for guidance about parallel programming. 具体的には、AsyncReaderWriterLock についての投稿で、同時読み取りアクセスを許可している間に、書き込みのためのファイルへの排他的アクセスを維持する方法について説明されています。In particular, the post about AsyncReaderWriterLock describes how to maintain exclusive access to a file for writes while allowing concurrent read access. I/O のシリアル化がパフォーマンスに影響することに注意してください。Keep in mind that serializing I/O will impact performance.

関連項目See also