14

JPEG画像でJPEGメタデータエンコーディングを実行するTPLタスクを作成するマネージド.Net/C#アプリケーションがあります。各タスクは、TaskCreationOptions.LongRunningオプションを使用して構築されます。

Task task = new Task( () => TaskProc(), cancelToken, TaskCreationOptions.LongRunning );

TaskProc()は、JpegBitmapDecoderクラスとJpegBitmapEncoderクラスを利用して、JPEGメタデータを追加し、新しい画像をディスクに保存します。このようなタスクは一度に最大2つアクティブにすることができ、このプロセスは無期限に続行する必要があります。

前述の操作をしばらく実行すると、JpegBitmapDecoderクラスのインスタンスを作成しようとすると、このコマンド例外を処理するのに十分なストレージが利用できなくなります。


System.ComponentModel.Win32Exception(0x80004005): MS.Win32.HwndWrapper..ctor(Int32 classStyle、Int32 style、Int32 exStyle、Int3 )のMS.Win32.UnsafeNativeMethods.RegisterClassEx(WNDCLASSEX_D wc_d)でこのコマンドを処理するのに十分なストレージがありません2 x、Int32 y、Int32幅、Int32高さ、文字列名、IntPtr親、HwndWrapperHoo k []フック)at System.Windows.Threading.Dispatcher..ctor()at System.Windows.Threading.Dispatcher.get_CurrentDispatcher()at System.Windows.Media.Imaging.JpegBitmapDecoder..ctor(StreambitmapStream、Bit mapCreateOptions createOptions、BitmapCacheOption cacheOption)のSystem.Windows.Media.Imaging.BitmapDecoder..ctor(StreambitmapStream、BitmapC reateOptions createOptions、BitmapCacheOption cacheOption、Guid expectedClsId)

このエラーは、JpegBitmapDecoderを使用してメタデータを追加した場合にのみ発生しました。つまり、タスクがビットマップイメージをエンコードしてファイルに保存するだけの場合、問題は発生しませんでした。Process Explorer、Process Monitor、またはその他の診断ツールを使用した場合、明らかなことは何も明らかになりませんでした。スレッド、メモリ、またはハンドルのリークはまったく観察されませんでした。このようなエラーが発生すると、メモ帳や単語などの新しいアプリケーションを起動できなくなります。アプリケーションが終了すると、すべてが通常の状態に戻ります。

LongRunningのタスク作成オプションは、MSDNで次のように定義されています。タスクが長時間実行される粗粒度の操作になることを指定します。これは、オーバーサブスクリプションが保証される可能性があるというヒントをTaskSchedulerに提供します。これは、タスクを実行するために選択されたスレッドがThreadPoolからのものではない可能性があることを意味します。つまり、タスクの目的で作成されます。他のタスク作成オプションでは、タスクにThreadPoolスレッドが選択されます。

しばらく分析してテストした後、タスク作成オプションをLongRunning以外のもの( PreferFairnessなど)に変更しました。コードに他の変更はまったく行われませんでした。これにより、問題が「解決」されました。つまり、ストレージエラーが不足することはなくなりました。

LongRunningスレッドが原因である実際の理由については困惑しています。これに関するいくつかの質問があります:

  1. タスクを実行するために選択されたスレッドがThreadPoolからのものであるかどうかという事実が必要なのはなぜですか?スレッドが終了した場合、そのリソースは、その起源に関係なく、GCによって時間の経過とともに再利用され、OSに戻されるべきではありませんか?

  2. LongRunningタスクとエラーの原因となるJpegBitmapDecoderの機能の組み合わせの何が特別なのですか?

4

2 に答える 2

18

名前空間のクラスはスレッド アーキテクチャSystem.Windows.Media.Imagingに基づいています。良くも悪くも、デフォルトの動作の一部は、コンポーネントが静的プロパティを介して現在のディスパッチャーを要求するたびに、実行中のスレッドで新しいを開始することです。これは、ディスパッチャーの「ランタイム」全体がスレッドに対して起動され、あらゆる種類のリソースが割り当てられ、適切にクリーンアップされない場合、マネージ リークが発生することを意味します。また、「ランタイム」は、実行中のスレッドが、標準メッセージのポンピングが行われている STA スレッドであると想定しており、ランタイムはデフォルトで STA スレッドを開始していません。DispatcherDispatcherDispatcher.CurrentDispatcherTask

では、「通常の」ThreadPool ベースのスレッドではなく、LongRunning で発生するのはなぜでしょうか? 原因 LongRunning は、毎回新しいスレッドをスピンアップしていることを意味します。つまり、毎回新しい Dispatcher リソースを意味します。最終的に、デフォルトのタスク スケジューラ (ThreadPool ベースのスケジューラ) を十分に長く実行すると、Dispatcherランタイムが必要なものをクリーンアップできるようにメッセージを送り出すものが何もないため、スペースが不足してしまいます。

したがって、このようなスレッドベースのクラスを使用する場合は、 「ランタイム」を適切に管理しているスレッドのプールでその種の作業を実行するように設計されDispatcherたカスタムを使用する必要があります。良いニュースは、あなたがここでつかむことができるものを私がすでに書いているので、あなたは幸運です. FWIW、私はこの実装を、1 日に数十万の画像を処理するプロダクション コードの 3 つの非常に大量の部分で使用しています。TaskSchedulerDispatcher

実装の更新

async最近、実装を再度更新したので、.NET 4.5の新機能と互換性があります。SynchronizationContext元の実装は、その必要がなかったため、概念と協力的ではありませんでした。Dispatcher スレッドで実行されているメソッド内で C#のキーワードを使用している可能性があるので、awaitそれと連携できるようにする必要があります。以前の実装ではこの状況でデッドロックが発生しましたが、この最新の実装ではそうではありません。

于 2012-09-27T19:02:00.507 に答える
8

Uri から BitmapSource オブジェクトを構築する際に、この問題を自分で再現して修正することができます。あなたと同じように、TaskCreationOptions.LongRunning.

この特定の状況でのリークを避けるために、必要な WPF オブジェクトをインスタンス化したらすぐに Dispatcher をシャットダウンできることがわかりました。

TaskProc の私の実用的な実装は次のとおりです。

private static BitmapImage TaskProc()
{
    var result = new BitmapImage(new Uri(@"c:\test.jpg"));
    // the following line fixes the problem, no more leaks occur
    result.Dispatcher.InvokeShutdown();
    return result;
}
于 2013-04-30T11:17:05.740 に答える