Fix race condition when getting the loader task
This commit is contained in:
1 parent
649f81494e
commit
466c443a20
1 file changed
+92
-75
@@ -346,7 +346,9 @@ namespace Vocaluxe.Lib.Draw
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the texture to the cache
|
/// Adds the texture to the cache<br />
|
||||||
|
/// Asserts that the cache entry does not exist yet. So make Get/Add cache atomic through use of _TextureCache lock!<br />
|
||||||
|
/// Thread safe
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="texture"></param>
|
/// <param name="texture"></param>
|
||||||
/// <param name="origSize"></param>
|
/// <param name="origSize"></param>
|
||||||
@@ -367,25 +369,37 @@ namespace Vocaluxe.Lib.Draw
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a new texture reference from the cache <br />
|
/// Gets a new texture reference from the cache <br />
|
||||||
/// Thread safe but you should hold the _TextureCache lock if you need the loader task
|
/// Thread safe
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="texturePath"></param>
|
/// <param name="texturePath"></param>
|
||||||
|
/// <param name="loader">Task that loads the texture or null if not loading</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private CTextureRef _GetFromCache(string texturePath)
|
private CTextureRef _GetFromCache(string texturePath, out Task<Size> loader)
|
||||||
{
|
{
|
||||||
|
loader = null;
|
||||||
if (String.IsNullOrEmpty(texturePath))
|
if (String.IsNullOrEmpty(texturePath))
|
||||||
return null;
|
return null;
|
||||||
lock (_Textures)
|
lock (_TextureCache)
|
||||||
{
|
lock (_Textures)
|
||||||
STextureCacheEntry cacheEntry;
|
|
||||||
if (_TextureCache.TryGetValue(texturePath, out cacheEntry))
|
|
||||||
{
|
{
|
||||||
if (cacheEntry.Texture.RefCount <= 0)
|
STextureCacheEntry cacheEntry;
|
||||||
_TextureCache.Remove(texturePath);
|
if (_TextureCache.TryGetValue(texturePath, out cacheEntry))
|
||||||
else
|
{
|
||||||
return _GetTextureReference(cacheEntry.OrigSize, cacheEntry.Texture);
|
if (cacheEntry.Texture.RefCount <= 0)
|
||||||
|
_TextureCache.Remove(texturePath);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (cacheEntry.OrigSize.Width < 0)
|
||||||
|
{
|
||||||
|
lock (_BitmapsLoading)
|
||||||
|
{
|
||||||
|
loader = _BitmapsLoading[texturePath];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return _GetTextureReference(cacheEntry.OrigSize, cacheEntry.Texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -522,47 +536,31 @@ namespace Vocaluxe.Lib.Draw
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Replaces one oldTexture by newTexture summing the refCounts. Also disposes oldTexture
|
/// Replaces one oldTexture by newTexture summing the refCounts. Also disposes oldTexture
|
||||||
/// IMPORTANT: Caller MUST hold the _Textures lock!
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="oldTexture"></param>
|
/// <param name="oldTexture"></param>
|
||||||
/// <param name="newTexture"></param>
|
/// <param name="newTexture"></param>
|
||||||
private void _MergeTextures(TTextureType oldTexture, TTextureType newTexture)
|
private void _MergeTextures(TTextureType oldTexture, TTextureType newTexture)
|
||||||
{
|
{
|
||||||
|
_EnsureMainThread();
|
||||||
Debug.Assert(oldTexture.RefCount > 0);
|
Debug.Assert(oldTexture.RefCount > 0);
|
||||||
newTexture.RefCount += oldTexture.RefCount;
|
lock (_Textures)
|
||||||
// ReSharper disable AccessToDisposedClosure
|
|
||||||
IEnumerable<int> oldKeys = _Textures.Where(pair => pair.Value == oldTexture).Select(pair => pair.Key).ToArray();
|
|
||||||
// ReSharper restore AccessToDisposedClosure
|
|
||||||
foreach (int key in oldKeys)
|
|
||||||
_Textures[key] = newTexture;
|
|
||||||
oldTexture.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if a texture is currently beeing loaded and waits for completion if it is <br />
|
|
||||||
/// Required for cached entries, but should not be called from inside any locks unless async is true
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="textureRef"></param>
|
|
||||||
/// <param name="filePath"></param>
|
|
||||||
/// <param name="async">If true, do not block</param>
|
|
||||||
private void _WaitForTextureLoaded(CTextureRef textureRef, string filePath, bool async)
|
|
||||||
{
|
|
||||||
// Check if texture is already loaded
|
|
||||||
if (textureRef.OrigSize.Width > 0)
|
|
||||||
return;
|
|
||||||
Task<Size> loader;
|
|
||||||
lock (_BitmapsLoading)
|
|
||||||
{
|
{
|
||||||
if (!_BitmapsLoading.TryGetValue(filePath, out loader))
|
newTexture.RefCount += oldTexture.RefCount;
|
||||||
Debug.Assert(false, "Corruption: Texture not loaded but no loader found");
|
IEnumerable<int> oldKeys = _Textures.Where(pair => pair.Value == oldTexture).Select(pair => pair.Key).ToArray();
|
||||||
|
foreach (int key in oldKeys)
|
||||||
|
_Textures[key] = newTexture;
|
||||||
|
if (!String.IsNullOrEmpty(oldTexture.TexturePath))
|
||||||
|
{
|
||||||
|
newTexture.TexturePath = oldTexture.TexturePath;
|
||||||
|
STextureCacheEntry cacheEntry;
|
||||||
|
if (_TextureCache.TryGetValue(newTexture.TexturePath, out cacheEntry))
|
||||||
|
{
|
||||||
|
cacheEntry.Texture = newTexture;
|
||||||
|
_TextureCache[newTexture.TexturePath] = cacheEntry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
oldTexture.Dispose();
|
||||||
}
|
}
|
||||||
// We found the texture in the cache, but its bitmap is not yet loaded
|
|
||||||
// So wait for completion and update the origSize once it is available
|
|
||||||
// Note: loader.Result blocks till completion of the task
|
|
||||||
if (async)
|
|
||||||
Task.Factory.StartNew(() => textureRef.OrigSize = loader.Result);
|
|
||||||
else
|
|
||||||
textureRef.OrigSize = loader.Result;
|
|
||||||
}
|
}
|
||||||
#endregion private/protected
|
#endregion private/protected
|
||||||
|
|
||||||
@@ -575,13 +573,15 @@ namespace Vocaluxe.Lib.Draw
|
|||||||
{
|
{
|
||||||
_EnsureMainThread();
|
_EnsureMainThread();
|
||||||
CTextureRef textureRef;
|
CTextureRef textureRef;
|
||||||
|
Task<Size> loader;
|
||||||
lock (_TextureCache)
|
lock (_TextureCache)
|
||||||
{
|
{
|
||||||
textureRef = _GetFromCache(texturePath);
|
textureRef = _GetFromCache(texturePath, out loader);
|
||||||
}
|
}
|
||||||
if (textureRef != null)
|
if (textureRef != null)
|
||||||
{
|
{
|
||||||
_WaitForTextureLoaded(textureRef, texturePath, false);
|
if (loader != null)
|
||||||
|
textureRef.OrigSize = loader.Result;
|
||||||
Debug.Assert(textureRef.OrigSize.Width > 0);
|
Debug.Assert(textureRef.OrigSize.Width > 0);
|
||||||
return textureRef;
|
return textureRef;
|
||||||
}
|
}
|
||||||
@@ -621,10 +621,11 @@ namespace Vocaluxe.Lib.Draw
|
|||||||
CTextureRef textureRef;
|
CTextureRef textureRef;
|
||||||
Size origSize = bmp.GetSize();
|
Size origSize = bmp.GetSize();
|
||||||
TTextureType texture = null;
|
TTextureType texture = null;
|
||||||
|
Task<Size> loader;
|
||||||
// Make the Get/Add Cache methods atomic
|
// Make the Get/Add Cache methods atomic
|
||||||
lock (_TextureCache)
|
lock (_TextureCache)
|
||||||
{
|
{
|
||||||
textureRef = _GetFromCache(texturePath);
|
textureRef = _GetFromCache(texturePath, out loader);
|
||||||
if (textureRef == null)
|
if (textureRef == null)
|
||||||
{
|
{
|
||||||
_WriteBitmapToTexture(ref texture, bmp);
|
_WriteBitmapToTexture(ref texture, bmp);
|
||||||
@@ -634,8 +635,8 @@ namespace Vocaluxe.Lib.Draw
|
|||||||
}
|
}
|
||||||
if (textureRef == null)
|
if (textureRef == null)
|
||||||
textureRef = _GetTextureReference(origSize, texture);
|
textureRef = _GetTextureReference(origSize, texture);
|
||||||
else
|
else if (loader != null)
|
||||||
_WaitForTextureLoaded(textureRef, texturePath, false);
|
textureRef.OrigSize = loader.Result;
|
||||||
Debug.Assert(textureRef.OrigSize.Width > 0);
|
Debug.Assert(textureRef.OrigSize.Width > 0);
|
||||||
return textureRef;
|
return textureRef;
|
||||||
}
|
}
|
||||||
@@ -693,13 +694,14 @@ namespace Vocaluxe.Lib.Draw
|
|||||||
|
|
||||||
CTextureRef textureRef;
|
CTextureRef textureRef;
|
||||||
TTextureType texture = null;
|
TTextureType texture = null;
|
||||||
Task<Size> loader = null;
|
Task<Size> loader;
|
||||||
// Make Get/Add cache atomic
|
// Make Get/Add cache atomic
|
||||||
lock (_TextureCache)
|
lock (_TextureCache)
|
||||||
{
|
{
|
||||||
textureRef = _GetFromCache(filePath);
|
textureRef = _GetFromCache(filePath, out loader);
|
||||||
if (textureRef == null)
|
if (textureRef == null)
|
||||||
{
|
{
|
||||||
|
Debug.Assert(loader == null);
|
||||||
Size invalidSize = new Size(-1, -1);
|
Size invalidSize = new Size(-1, -1);
|
||||||
texture = _CreateTexture(invalidSize);
|
texture = _CreateTexture(invalidSize);
|
||||||
// This cache entry has to be updated once the real origSize is available
|
// This cache entry has to be updated once the real origSize is available
|
||||||
@@ -718,7 +720,9 @@ namespace Vocaluxe.Lib.Draw
|
|||||||
if (texture == null)
|
if (texture == null)
|
||||||
{
|
{
|
||||||
// Found in cache
|
// Found in cache
|
||||||
_WaitForTextureLoaded(textureRef, filePath, true);
|
CTextureRef tmp = textureRef; // Workaround for implicitly captured closure warning (false positive)
|
||||||
|
if (loader != null)
|
||||||
|
Task.Factory.StartNew(() => tmp.OrigSize = loader.Result);
|
||||||
return textureRef;
|
return textureRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -736,42 +740,46 @@ namespace Vocaluxe.Lib.Draw
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private Size _LoadAndEnqueueBitmap(string filePath, CTextureRef textureRef)
|
private Size _LoadAndEnqueueBitmap(string filePath, CTextureRef textureRef)
|
||||||
{
|
{
|
||||||
Size origSize;
|
|
||||||
Bitmap bmp = CHelper.LoadBitmap(filePath);
|
Bitmap bmp = CHelper.LoadBitmap(filePath);
|
||||||
if (bmp == null)
|
if (bmp == null)
|
||||||
{
|
{
|
||||||
RemoveTexture(ref textureRef); // Done asynchonously in the function
|
RemoveTexture(ref textureRef); // Done asynchonously in the function
|
||||||
origSize = new Size(-1, -1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
origSize = bmp.GetSize();
|
|
||||||
textureRef.OrigSize = origSize;
|
|
||||||
// Update cache, use the same lock as in add/get cache methods
|
|
||||||
lock (_Textures)
|
lock (_Textures)
|
||||||
{
|
{
|
||||||
STextureCacheEntry cacheEntry;
|
_TextureCache.Remove(filePath);
|
||||||
if (_TextureCache.TryGetValue(filePath, out cacheEntry))
|
lock (_BitmapsLoading)
|
||||||
{
|
{
|
||||||
cacheEntry.OrigSize = origSize;
|
_BitmapsLoading.Remove(filePath);
|
||||||
_TextureCache[filePath] = cacheEntry;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Size size = _GetNewTextureSize(bmp.GetSize());
|
return new Size(-1, -1);
|
||||||
if (!size.Equals(bmp.GetSize()))
|
}
|
||||||
|
Size origSize = bmp.GetSize();
|
||||||
|
textureRef.OrigSize = origSize;
|
||||||
|
// Update cache, use the same lock as in add/get cache methods
|
||||||
|
lock (_Textures)
|
||||||
|
{
|
||||||
|
STextureCacheEntry cacheEntry;
|
||||||
|
if (_TextureCache.TryGetValue(filePath, out cacheEntry))
|
||||||
{
|
{
|
||||||
Bitmap bmp2 = bmp.Resize(size);
|
cacheEntry.OrigSize = origSize;
|
||||||
bmp.Dispose();
|
_TextureCache[filePath] = cacheEntry;
|
||||||
bmp = bmp2;
|
|
||||||
}
|
}
|
||||||
lock (_TextureQueue)
|
lock (_BitmapsLoading)
|
||||||
{
|
{
|
||||||
_TextureQueue.Enqueue(new STextureQueue(textureRef, EQueueAction.Add, bmp));
|
_BitmapsLoading.Remove(filePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lock (_BitmapsLoading)
|
Size size = _GetNewTextureSize(origSize);
|
||||||
|
if (!size.Equals(origSize))
|
||||||
{
|
{
|
||||||
_BitmapsLoading.Remove(filePath);
|
Bitmap bmp2 = bmp.Resize(size);
|
||||||
|
bmp.Dispose();
|
||||||
|
bmp = bmp2;
|
||||||
|
}
|
||||||
|
lock (_TextureQueue)
|
||||||
|
{
|
||||||
|
_TextureQueue.Enqueue(new STextureQueue(textureRef, EQueueAction.Add, bmp));
|
||||||
}
|
}
|
||||||
return origSize;
|
return origSize;
|
||||||
}
|
}
|
||||||
@@ -840,7 +848,16 @@ namespace Vocaluxe.Lib.Draw
|
|||||||
return null;
|
return null;
|
||||||
// If bitmap is not yet loaded, wait for it
|
// If bitmap is not yet loaded, wait for it
|
||||||
if (!String.IsNullOrEmpty(texture.TexturePath))
|
if (!String.IsNullOrEmpty(texture.TexturePath))
|
||||||
_WaitForTextureLoaded(textureRef, texture.TexturePath, false);
|
{
|
||||||
|
Task<Size> loader;
|
||||||
|
lock (_BitmapsLoading)
|
||||||
|
{
|
||||||
|
_BitmapsLoading.TryGetValue(texture.TexturePath, out loader);
|
||||||
|
}
|
||||||
|
if (loader != null)
|
||||||
|
textureRef.OrigSize = loader.Result;
|
||||||
|
}
|
||||||
|
Debug.Assert(textureRef.OrigSize.Width > 0);
|
||||||
return _GetTextureReference(textureRef.OrigSize, texture);
|
return _GetTextureReference(textureRef.OrigSize, texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user