/// <summary> /// Executes a <see cref="ThreadPoolAsyncCallbackState"/> on the <see cref="ThreadPool"/> by passing it to the /// <see cref="ThreadPoolCallback"/> method. If it fails to be added to the <see cref="ThreadPool"/> for whatever /// reason, it will execute synchronously. /// </summary> /// <param name="state">The <see cref="ThreadPoolAsyncCallbackState"/> describing the state and operation to perform.</param> void ExecuteOnThreadPool(ThreadPoolAsyncCallbackState state) { bool wasEnqueued; try { // Try to add to the thread pool wasEnqueued = ThreadPool.QueueUserWorkItem(_threadPoolCallback, state); if (!wasEnqueued) { const string errmsg = "Failed to create thread to generate image for GrhData `{0}`" + " - running synchronously instead. ThreadPool.QueueWorkItem() returned false."; if (log.IsInfoEnabled) { log.InfoFormat(errmsg, state.GrhData); } } } catch (OutOfMemoryException ex) { wasEnqueued = false; if (!wasEnqueued) { const string errmsg = "Failed to create thread to generate image for GrhData `{0}`" + " - running synchronously instead. Exception: {1}"; if (log.IsInfoEnabled) { log.InfoFormat(errmsg, state.GrhData, ex); } } } catch (ApplicationException ex) { wasEnqueued = false; const string errmsg = "Failed to create thread to generate image for GrhData `{0}`" + " - running synchronously instead. Exception: {1}"; if (log.IsInfoEnabled) { log.InfoFormat(errmsg, state.GrhData, ex); } } // If we failed to enqueue it on the thread pool, run it synchronously if (!wasEnqueued) { ThreadPoolCallback(state); } }
/// <summary> /// Gets the <see cref="Image"/> for the given argument. /// </summary> /// <param name="gd">The <see cref="StationaryGrhData"/> to get the <see cref="Image"/> for.</param> /// <param name="async">If true, asynchronous mode will be used. This will return null immediately if the desired /// <see cref="Image"/> has not yet been created.</param> /// <param name="callback">When <see cref="async"/> is false, contains the callback method to invoke when the <see cref="Image"/> /// has been created.</param> /// <param name="userState">The optional user state object to pass to the <paramref name="callback"/>.</param> /// <returns> /// The <see cref="Image"/> for the <paramref name="gd"/>, or null if <paramref name="async"/> is set. /// </returns> Image GetImage(StationaryGrhData gd, bool async, GrhImageListAsyncCallback callback, object userState) { if (gd == null) { if (!async) { // Return the ErrorImage directly return ErrorImage; } else { // Raise the callback and pass the ErrorImage if (callback != null) callback(this, gd, ErrorImage, userState); return null; } } // Get the key var key = GetImageKey(gd); // Get the image from the cache Image img; lock (_imagesSync) { // Check if the image already exists if (!_images.TryGetValue(key, out img)) { // Image does not exist, so add the placeholder since we are about to create it. Placing the placeholder // in there will make sure that no other threads try to create it at the same time. img = null; _images.Add(key, _placeholder); } } if (!async) { if (img != null) { if (img == _placeholder) { // If we got the placeholder image, do a spin-wait until we get the actual image, then return the image. This will // happen when another thread is creating the image. return SpinWaitForImage(key); } else { // Any other non-null image means that the image was already created, so we can just return it immediately return img; } } else { // Create it on this thread and return it when its done return CreateAndInsertImage(key, gd); } } else { if (img != null) { // When we get the placeholder image while in async mode, this is slightly more annoying since we have // to create another thread to spin-wait on if (img == _placeholder) { // Create the thread to spin-wait on... though only if we were given a callback method. Obviously does no // good to wait for the image when there is no callback method. if (callback != null) { var tpacs = new ThreadPoolAsyncCallbackState(gd, callback, userState, true, null); ExecuteOnThreadPool(tpacs); } } else { // But when we get the actual image, we can just invoke the callback directly from this thread. This is the // once scenario where no threads are created in async mode. if (callback != null) callback(this, gd, img, userState); } } else { // NOTE: The asynchronous aspect is less than optimal due to this. // When originally designing this, I was working under the assumption that SFML would be able to deal with the threading // better. Turns out I was wrong. There are probably some other threading issues that would have to be taken into // account, too, like that content can be disposed on the main thread. I could offload more onto the worker thread // than just the rescaling, such as the generation of the original unscaled bitmap, but the biggest gains come from // the ability to offload the actual image loading. I guess its helpful to have at least a little work offloaded // than to be completely synchronous since that does mean multi-core CPUs can load a bit faster. var bmp = CreateUnscaledBitmap(gd); if (bmp == null) { // If the bitmap failed to be created for whatever reason, use the ErrorImage if (callback != null) callback(this, gd, ErrorImage, userState); lock (_imagesSync) { Debug.Assert(_images[key] == _placeholder); _images[key] = ErrorImage; } } else { // Add the Image creation job to the thread pool var tpacs = new ThreadPoolAsyncCallbackState(gd, callback, userState, false, bmp); ExecuteOnThreadPool(tpacs); } } // Async always returns null return null; } }
/// <summary> /// Executes a <see cref="ThreadPoolAsyncCallbackState"/> on the <see cref="ThreadPool"/> by passing it to the /// <see cref="ThreadPoolCallback"/> method. If it fails to be added to the <see cref="ThreadPool"/> for whatever /// reason, it will execute synchronously. /// </summary> /// <param name="state">The <see cref="ThreadPoolAsyncCallbackState"/> describing the state and operation to perform.</param> void ExecuteOnThreadPool(ThreadPoolAsyncCallbackState state) { bool wasEnqueued; try { // Try to add to the thread pool wasEnqueued = ThreadPool.QueueUserWorkItem(_threadPoolCallback, state); if (!wasEnqueued) { const string errmsg = "Failed to create thread to generate image for GrhData `{0}`" + " - running synchronously instead. ThreadPool.QueueWorkItem() returned false."; if (log.IsInfoEnabled) log.InfoFormat(errmsg, state.GrhData); } } catch (OutOfMemoryException ex) { wasEnqueued = false; if (!wasEnqueued) { const string errmsg = "Failed to create thread to generate image for GrhData `{0}`" + " - running synchronously instead. Exception: {1}"; if (log.IsInfoEnabled) log.InfoFormat(errmsg, state.GrhData, ex); } } catch (ApplicationException ex) { wasEnqueued = false; const string errmsg = "Failed to create thread to generate image for GrhData `{0}`" + " - running synchronously instead. Exception: {1}"; if (log.IsInfoEnabled) log.InfoFormat(errmsg, state.GrhData, ex); } // If we failed to enqueue it on the thread pool, run it synchronously if (!wasEnqueued) ThreadPoolCallback(state); }
/// <summary> /// Gets the <see cref="Image"/> for the given argument. /// </summary> /// <param name="gd">The <see cref="StationaryGrhData"/> to get the <see cref="Image"/> for.</param> /// <param name="async">If true, asynchronous mode will be used. This will return null immediately if the desired /// <see cref="Image"/> has not yet been created.</param> /// <param name="callback">When <see cref="async"/> is false, contains the callback method to invoke when the <see cref="Image"/> /// has been created.</param> /// <param name="userState">The optional user state object to pass to the <paramref name="callback"/>.</param> /// <returns> /// The <see cref="Image"/> for the <paramref name="gd"/>, or null if <paramref name="async"/> is set. /// </returns> Image GetImage(StationaryGrhData gd, bool async, GrhImageListAsyncCallback callback, object userState) { if (gd == null) { if (!async) { // Return the ErrorImage directly return(ErrorImage); } else { // Raise the callback and pass the ErrorImage if (callback != null) { callback(this, gd, ErrorImage, userState); } return(null); } } // Get the key var key = GetImageKey(gd); // Get the image from the cache Image img; lock (_imagesSync) { // Check if the image already exists if (!_images.TryGetValue(key, out img)) { // Image does not exist, so add the placeholder since we are about to create it. Placing the placeholder // in there will make sure that no other threads try to create it at the same time. img = null; _images.Add(key, _placeholder); } } if (!async) { if (img != null) { if (img == _placeholder) { // If we got the placeholder image, do a spin-wait until we get the actual image, then return the image. This will // happen when another thread is creating the image. return(SpinWaitForImage(key)); } else { // Any other non-null image means that the image was already created, so we can just return it immediately return(img); } } else { // Create it on this thread and return it when its done return(CreateAndInsertImage(key, gd)); } } else { if (img != null) { // When we get the placeholder image while in async mode, this is slightly more annoying since we have // to create another thread to spin-wait on if (img == _placeholder) { // Create the thread to spin-wait on... though only if we were given a callback method. Obviously does no // good to wait for the image when there is no callback method. if (callback != null) { var tpacs = new ThreadPoolAsyncCallbackState(gd, callback, userState, true, null); ExecuteOnThreadPool(tpacs); } } else { // But when we get the actual image, we can just invoke the callback directly from this thread. This is the // once scenario where no threads are created in async mode. if (callback != null) { callback(this, gd, img, userState); } } } else { // NOTE: The asynchronous aspect is less than optimal due to this. // When originally designing this, I was working under the assumption that SFML would be able to deal with the threading // better. Turns out I was wrong. There are probably some other threading issues that would have to be taken into // account, too, like that content can be disposed on the main thread. I could offload more onto the worker thread // than just the rescaling, such as the generation of the original unscaled bitmap, but the biggest gains come from // the ability to offload the actual image loading. I guess its helpful to have at least a little work offloaded // than to be completely synchronous since that does mean multi-core CPUs can load a bit faster. var bmp = CreateUnscaledBitmap(gd); if (bmp == null) { // If the bitmap failed to be created for whatever reason, use the ErrorImage if (callback != null) { callback(this, gd, ErrorImage, userState); } lock (_imagesSync) { Debug.Assert(_images[key] == _placeholder); _images[key] = ErrorImage; } } else { // Add the Image creation job to the thread pool var tpacs = new ThreadPoolAsyncCallbackState(gd, callback, userState, false, bmp); ExecuteOnThreadPool(tpacs); } } // Async always returns null return(null); } }