次の方法で共有


ファイルへの書き込みに関するベスト プラクティス

重要な API

開発者は、ファイルシステムの I/O 操作を実行するために、FileIO および PathIO クラスの Write メソッドを使用する際、よくある問題に遭遇することがあります。 たとえば、一般的な問題は次のとおりです。

  • ファイルが部分的に書き込まれます。
  • いずれかのメソッドを呼び出すと、アプリは例外を受け取ります。
  • 操作により、ターゲットファイル名に似たファイル名の.TMPファイルが生成されます。

FileIO クラスと PathIO クラスの Write メソッドには、次のものが含まれます。

  • WriteBufferAsync
  • WriteBytesAsync の
  • WriteLinesAsync の
  • 書き込みテキスト非同期

この記事では、開発者がそれらをいつどのように使用するかを理解できるように、これらのメソッドがどのように機能するかについて詳しく説明します。 この記事では、ガイドラインを提供し、考えられるすべてのファイル I/O 問題の解決策を提供しようとはしません。

 この記事では、例やディスカッションを通じてFileIO メソッドに焦点を当てます。 ただし、PathIO メソッドも同様のパターンに従っており、この記事のほとんどのガイダンスは、これらのメソッドにも適用されます。

利便性と制御

StorageFile オブジェクトは、ネイティブ Win32 プログラミング モデルのようなファイル ハンドルではありません。 代わりに、StorageFile は、その内容を操作するメソッドを持つファイルの表現です。

この概念を理解することは、StorageFileで I/O を実行する場合に便利です。 たとえば、「ファイルへの書き込み」セクションでは、ファイルに書き込む方法が 3 つあります。

最初の 2 つのシナリオは、アプリで最もよく使用されるシナリオです。 1 回の操作でファイルに書き込む方がコーディングと保守が容易になり、ファイル I/O の複雑さの多くを処理するアプリの責任も取り除きます。 ただし、この利便性にはコストがかかります。操作全体の制御が失われ、特定の時点でエラーをキャッチする機能が失われます。

トランザクション モデル

FileIOPathIO クラスの Write メソッドは、上記の 3 番目の書き込みモデルの手順を、レイヤーを追加してラップします。 このレイヤーは、ストレージ トランザクションにカプセル化されます。

データの書き込み中に問題が発生した場合に元のファイルの整合性を保護するために、Write メソッドでは、OpenTransactedWriteAsyncを使用してファイル 開くことでトランザクション モデルを使用します。 このプロセスにより、StorageStreamTransaction オブジェクトが作成されます。 このトランザクション オブジェクトが作成されると、API は、StorageStreamTransaction 記事の File Access サンプルまたはコード例と同様の方法でデータを書き込みます。

次の図は、正常な書き込み操作で WriteTextAsync メソッドによって実行される基になるタスクを示しています。 この図は、操作の簡略化されたビューを提供します。 たとえば、さまざまなスレッドでテキスト エンコードや非同期補完などの手順をスキップします。

ファイル に書き込むための UWP API 呼び出しシーケンス図

ストリームを使用するより複雑な 4 ステップ モデルの代わりに、FileIO クラスと PathIO クラスの Write メソッドを使用する利点は次のとおりです。

  • エラーを含むすべての中間ステップを処理する 1 つの API 呼び出し。
  • 問題が発生した場合、元のファイルは保持されます。
  • システムの状態は、できるだけクリーンに保たれます。

ただし、非常に多くの中間障害点が発生する可能性があるため、障害の可能性が高くなります。 エラーが発生した場合、プロセスが失敗した場所を理解するのが難しい場合があります。 次のセクションでは、Write メソッドを使用するときに発生する可能性のあるエラーの一部を示し、考えられる解決策を提供します。

FileIO クラスと PathIO クラスの Write メソッドに関する一般的なエラー コード

次の表は、Write メソッドを使用するときにアプリ開発者が遭遇する一般的なエラー コードを示しています。 表の手順は、前の図の手順に対応しています。

エラー名 (値) ステップス 原因 解決方法
ERROR_ACCESS_DENIED (0X80070005) アクセスが拒否されました 5 元のファイルは、削除対象としてマークされている可能性があります。以前の操作の場合もあります。 操作を再試行します。
ファイルへのアクセスが同期されていることを確認します。
ERROR_SHARING_VIOLATION (共有違反エラー) 0x80070020 5 元のファイルは、別の排他的書き込みによって開かれます。 操作を再試行します。
ファイルへのアクセスが同期されていることを確認します。
置換済みのものを削除できません (0x80070497) 19-20 元のファイル (file.txt) は使用中であるため、置き換えませんでした。 別のプロセスまたは操作が、置き換えられる前にファイルへのアクセスを取得しました。 操作を再試行します。
ファイルへのアクセスが同期されていることを確認します。
ディスクがいっぱいですエラー (0x80070070) 7, 14, 16, 20 トランザクション モデルによって追加のファイルが作成され、追加のストレージが消費されます。
メモリ不足エラー (ERROR_OUTOFMEMORY: 0x8007000E) 14, 16 これは、複数の未処理の I/O 操作または大きなファイル サイズが原因で発生する可能性があります。 ストリームを制御することで、より細かい方法でエラーが解決される可能性があります。
E_FAIL (0x80004005) [任意] その他 操作を再試行してください。 それでも失敗した場合は、プラットフォーム エラーである可能性があり、一貫性のない状態であるため、アプリを終了する必要があります。

エラーにつながる可能性があるファイルの状態に関するその他の考慮事項

Write メソッドによって返されるエラーとは別に、ファイルへの書き込み時にアプリが期待できることに関するいくつかのガイドラインを次に示します。

操作が完了した場合にのみ、データがファイルに書き込まれた

書き込み操作の進行中は、アプリでファイル内のデータについて何も想定しないでください。 操作が完了する前にファイルにアクセスしようとすると、データが不整合になる可能性があります。 アプリは、未処理の I/O を追跡する必要があります。

読者

書き込みを行うファイルが、礼儀正しいリーダー (つまり、FileAccessMode.Readで開かれた) によって使用されている場合、後続の読み取りはエラー ERROR_OPLOCK_HANDLE_CLOSED (0x80070323) で失敗します。 書き込み 操作が進行中に、アプリがファイルを開き直して読み取りを再試行することがあります。 これにより、元のファイルを置き換えることができないため、書き込み が最終的に失敗する競合状態が発生する可能性があります。

KnownFolders のファイル

KnownFoldersに存在するファイルにアクセスしようとしているのは、あなたのアプリだけではないかもしれません。 操作が成功したとしても、アプリがファイルに書き込んだコンテンツが、次回ファイルを読み取ろうとしたときに同じである保証はありません。 また、このシナリオでは、共有またはアクセス拒否エラーがより一般的になります。

I/O の競合

アプリがローカル データ内のファイルに対して Write メソッドを使用している場合、コンカレンシー エラーが発生する可能性は低くなりますが、それでも注意が必要です。 複数の 書き込み 操作がファイルに同時に送信されている場合、ファイル内のデータがどのような結果になるのかは保証されません。 これを軽減するには、アプリが書き込み 操作をファイルにシリアル化することをお勧めします。

~TMP ファイル

場合によっては、操作が強制的に取り消された場合 (たとえば、アプリが OS によって中断または終了された場合)、トランザクションは適切にコミットまたは閉じられない場合があります。 これにより、拡張子が (.~TMP) のファイルが残される可能性があります。 アプリのアクティブ化を処理するときに、これらの一時ファイル (アプリのローカル データに存在する場合) を削除することを検討してください。

ファイルの種類に基づく考慮事項

一部のエラーは、ファイルの種類、アクセス頻度、およびファイル サイズに応じて、より一般的になる可能性があります。 一般に、アプリがアクセスできるファイルには、次の 3 つのカテゴリがあります。

  • アプリのローカル データ フォルダー内のユーザーによって作成および編集されたファイル。 これらは、アプリの使用中にのみ作成および編集され、アプリ内にのみ存在します。
  • アプリのメタデータ。 アプリでは、これらのファイルを使用して、独自の状態を追跡します。
  • アプリがアクセスする機能を宣言しているファイル システムの場所にある他のファイル。 これらは最も一般的に、KnownFoldersのいずれかに配置されます。

あなたのアプリは、ファイルの最初の2つのカテゴリを完全に制御できます。これらのファイルは、アプリのパッケージファイルの一部であり、アプリによって排他的にアクセスされるためです。 最後のカテゴリのファイルの場合、他のアプリと OS サービスが同時にファイルにアクセスしている可能性があることをアプリで認識する必要があります。

アプリによっては、ファイルへのアクセスの頻度が異なる場合があります。

  • 非常に低い。 これらは通常、アプリの起動時に 1 回開き、アプリが中断されたときに保存されるファイルです。
  • 低い。 これらは、ユーザーが特にアクションを実行しているファイルです (保存や読み込みなど)。
  • 中または高。 これらは、アプリが常にデータを更新する必要があるファイルです (自動保存機能や一定のメタデータ追跡など)。

ファイル サイズについては、WriteBytesAsync メソッドの次のグラフのパフォーマンス データを検討してください。 このグラフでは、操作とファイル サイズの完了までの時間を比較します。制御された環境では、ファイル サイズあたりの平均パフォーマンスは 1,0000 操作です。

WriteBytesAsync パフォーマンス

ハードウェアと構成が異なると絶対時間値が異なるため、y 軸の時間値は意図的にこのグラフから省略されます。 ただし、テストでは一貫してこれらの傾向を確認しています。

  • 非常に小さいファイル (<= 1 MB): 操作を完了する時間は常に高速です。
  • 大きいファイル (> 1 MB): 操作を完了するまでの時間が指数関数的に増加し始めます。

アプリの中断中の I/O

後のセッションで使用するために状態情報またはメタデータを保持する場合は、中断を処理するようにアプリを設計する必要があります。 アプリの中断に関する背景情報については、アプリのライフサイクル を参照するか、このブログ投稿 を参照してください。

OS がアプリに拡張実行を許可しない限り、アプリが一時停止されると、すべてのリソースを解放してデータを保存するのに 5 秒です。 最高の信頼性とユーザー エクスペリエンスを得るために、中断タスクを処理する必要がある時間は常に制限されていると想定してください。 中断タスクを処理するための 5 秒間の次のガイドラインに注意してください。

  • フラッシュ操作と解放操作によって発生する競合状態を回避するために、I/O を最小限に抑えてみてください。
  • 書き込みに数百ミリ秒以上必要なファイルの書き込みは避けてください。
  • アプリで Write メソッドを使用する場合は、これらのメソッドに必要なすべての中間手順に注意してください。

中断中にアプリが少量の状態データで動作する場合、ほとんどの場合、Write メソッドを使用してデータをフラッシュできます。 ただし、アプリで大量の状態データを使用する場合は、ストリームを使用してデータを直接格納することを検討してください。 これにより、書き込み メソッドのトランザクション モデルによって発生する遅延を減らすことができます。

例については、BasicSuspension サンプルを参照してください。

その他の例とリソース

特定のシナリオの例とその他のリソースを次に示します。

ファイル I/O の再試行のコード例

ユーザーが保存用のファイルを選択した後に書き込みを実行すると仮定して、書き込みを再試行する方法 (C#) の擬似コード例を次に示します。

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.
}

ファイルへのアクセスを同期する

.NET を使用した 並列プログラミングのブログ は、並列プログラミングに関するガイダンスに関する優れたリソースです。 特に、AsyncReaderWriterLock に関する 投稿では、同時読み取りアクセスを許可しながら、書き込み用にファイルへの排他アクセスを維持する方法について説明しています。 I/O をシリアル化するとパフォーマンスに影響することに注意してください。

こちらも参照ください