あなたが私を助けてくれることを願っています:)
バックグラウンド:
いくつかの特定の機能を備えた比較的基本的な画像ビューアー アプリケーションを C# で作成しています。
目標の 1 つは、比較的大きな画像 (〜 8 MB、実際には .jpg のみ) を十分な速さで読み込み、遅延のないブラウジング エクスペリエンス (左矢印キーと右矢印キーで画像をめくる) を提供することです。
私の解決策:
これを実現するために、サーキュラー バッファーを使用して BufferManager クラスを作成しました。バッファが満たされた状態を維持する無限ループである MaintainBuffer メソッドを持つクラス。現在表示されている画像の左側と右側の両方のバッファーに最小 K 個の画像があるかどうかを確認するだけで、存在しない場合は画像が存在するまでバッファーに読み込みます。ここで、新しい Thread でこの MaintainBuffer メソッドを自然に呼び出します。これにより、ユーザーは BufferManager がバックグラウンドで作業していることに気付かずに画像をめくることができ、アプリケーションはバッファーからユーザーの画像を簡単に表示できます。
私の解決策の問題:
私の問題は、Thread2 によるバッファーへの画像の読み込みが Thread1 (メイン スレッド) を遅くしているため、ユーザーが新しい画像に切り替えるたびに、新しい画像がバッファーに読み込まれている間に小さなラグスパイクが発生することです。
私のデバッグの試み:
タスク マネージャーをチェックして、スレッドが同じコアまたはそのような奇妙なもので実行されていないことを確認しましたが、2 つの異なるコアでの使用量に 2 つの明らかなスパイクが見られます。2 つのスレッドは一部のメモリを共有していますが、その中にはバッファがあるので、おそらくそれが問題であると考え、一時変数を使用して参照をバッファに移動するだけでしたが、役に立ちませんでした (最適化でしょうか?)。
ここで、IO操作がすべてのスレッドを遅くすることを漠然とほのめかしたものを読みましたが、CPUがファイルをメインメモリに移動することに実際に関与するべきではなく、Thread2のみがIO操作を待機する必要があるため、あまり意味がありません終わる。
私の質問:
- Thread2 のみが IO 操作によって遅くなるという仮定は間違っていますか?
- 問題は、スレッド間のデータ共有でしょうか? もしそうなら、どうすれば解決できますか?
- 他に何が問題を引き起こしている可能性がありますか? マルチスレッドの理解において、何か基本的なことが欠けていますか?
- 私がやろうとしていることよりも良い解決策があるでしょうか?
コード:
Form1 クラスでのスレッドの呼び出し:
private void browser_Load(object sender, EventArgs e)
{
//This is just getting the image and other file filenames (others needed for a feature)
string[] files = Directory.GetFiles(path);
imageSets = new Dictionary<string, List<string>>();
List<string> associatedFiles;
foreach (string f in files)
{
string filename = System.IO.Path.GetFileNameWithoutExtension(f);
string filenameExt = System.IO.Path.GetFileName(f);
if (imageSets.ContainsKey(filename))
{
imageSets.TryGetValue(filename, out associatedFiles);
associatedFiles.Add(filenameExt);
}
else
{
imageSets.Add(filename, new List<string>(){filenameExt});
}
}
//Getting only the names of the images
images = imageSets.Keys.ToList<string>();
//Creating a new bufferManager, note that the 'images' list is used by the object for getting the names of the images to load
bufferManager = new BufferManager(ref images, path);
//Have the bufferManager load the first image and put it in the buffer
currentImage = bufferManager.Init();
//Create a thread for maintaining the buffer
bufferThread = new Thread(bufferManager.MaintainBuffer);
bufferThread.Start();
//Display the intial image to the user
img_preview.BackgroundImage = currentImage;
}
BufferManager の初期化メソッド:
public Image Init()
{
currentIndex = lastBufferIndex = firstBufferIndex = 0;
Image fullSize = ImageTool.FromFile(path + "\\" + images[0] + ".jpg");
//Resize the image in the buffer to save memory and display faster
imageBuffer[0] = ImageTool.Resize(fullSize, 460); //The buffer is a simple array of fixed size
fullSize.Dispose();
return imageBuffer[0];
}
ここで興味深い方法: Thread2 で呼び出される MaintainBuffer
public void MaintainBuffer()
{
if (images.Count <= totalBufferSize)
{
//If the buffer can hold all the images without swapping images ind out, just load them all and dont worry about maintaining the buffer
totalBufferSize = images.Count;
for (int i = 0; i < totalBufferSize; ++i)
{
imageBuffer[i] = ImageTool.Resize(ImageTool.FromFile(path + "\\" + images[i] + ".jpg"), 460);
}
return;
}
while (!killBuffer)
{
//Ensure that enough images are buffered to the left of the current image. This looks a bit advanced due to being a circulair buffer, but the idea is simple
while (currentBufferIndex >= firstBufferIndex ? (currentBufferIndex - firstBufferIndex) < bufferWidth : (currentBufferIndex + totalBufferSize - firstBufferIndex) < bufferWidth)
{
--firstBufferIndex;
if (firstBufferIndex < 0)
firstBufferIndex = totalBufferSize - 1;
if (imageBuffer[firstBufferIndex] != null)
imageBuffer[firstBufferIndex].Dispose(); //Release the memory used by the image that is now "overwritten"
//Getting the image index of the image to load next
int imageNameIndex = currentIndex - (currentBufferIndex >= firstBufferIndex ? currentBufferIndex - firstBufferIndex : currentBufferIndex + totalBufferSize - firstBufferIndex);
if (imageNameIndex < 0)
imageNameIndex = images.Count + imageNameIndex;
Image fullSize = ImageTool.FromFile(path + "\\" + images[imageNameIndex] + ".jpg");
imageBuffer[firstBufferIndex] = ImageTool.Resize(fullSize, 460);
fullSize.Dispose();
}
//Ensure that enough images are buffered to the right of the current image
while (currentBufferIndex <= lastBufferIndex ? (lastBufferIndex - currentBufferIndex) < bufferWidth : (lastBufferIndex + totalBufferSize - currentBufferIndex) < bufferWidth)
{
++lastBufferIndex;
if (lastBufferIndex > totalBufferSize - 1)
lastBufferIndex = 0;
if (imageBuffer[lastBufferIndex] != null)
imageBuffer[lastBufferIndex].Dispose();
int imageNameIndex = (currentIndex + (currentBufferIndex <= lastBufferIndex ? lastBufferIndex - currentBufferIndex : lastBufferIndex + totalBufferSize - currentBufferIndex)) % (images.Count);
Image fullSize = ImageTool.FromFile(path + "\\" + images[imageNameIndex] + ".jpg");
imageBuffer[lastBufferIndex] = ImageTool.Resize(fullSize, 460);
fullSize.Dispose();
}
Thread.Sleep(100);
}
}
画像を左右に反転するコードは非常に単純なので省略しました。ただし、Thread1 の BufferManager オブジェクトの currentIndex (atm で表示されている画像のインデックス) と currentBufferIndex (サーキュラー バッファー内の現在の画像の位置) を更新します。これは、問題を引き起こす可能性のあるデータ共有の例です。
お気づきかもしれませんが、私はマルチスレッドの経験があまりないので、何かアドバイスをいただければ幸いです。ありがとう