/// <summary> /// Called whenever a new value is ready to be retrieved from /// the IDataSource. /// </summary> public override async Task OnNewResultImpl( IDataSource <CloseableReference <CloseableImage> > dataSource) { if (!dataSource.IsFinished()) { return; } CloseableReference <CloseableImage> closeableImageRef = dataSource.GetResult(); SoftwareBitmap bitmap = null; if (closeableImageRef != null && (closeableImageRef.Get().GetType() == typeof(CloseableBitmap) || closeableImageRef.Get().GetType() == typeof(CloseableStaticBitmap))) { bitmap = ((CloseableBitmap)closeableImageRef.Get()).UnderlyingBitmap; } try { await OnNewResultImpl(bitmap).ConfigureAwait(false); } finally { CloseableReference <CloseableImage> .CloseSafely(closeableImageRef); } }
public void TestOnBitmapCreated() { _platformBitmapFactory.AddBitmapReference(_bitmapReference.Get(), null); object callerContext = null; Assert.IsTrue(_cache._otherEntries.TryGetValue(_bitmapReference.Get(), out callerContext)); }
public void TestGet() { CloseableReference <byte[]> arrayRef = _array.Get(1); Assert.AreSame(_array._byteArraySoftRef.Get(), arrayRef.Get()); Assert.AreEqual(4, arrayRef.Get().Length); Assert.AreEqual(0, _array._semaphore.CurrentCount); }
/// <summary> /// Reads one byte. /// </summary> public byte Read(int offset) { lock (_poolGate) { EnsureValid(); Preconditions.CheckArgument(offset >= 0); Preconditions.CheckArgument(offset < _size); return(_bufRef.Get().Read(offset)); } }
public void TestGet_Realloc() { CloseableReference <byte[]> arrayRef = _pool.Get(1); byte[] smallArray = arrayRef.Get(); arrayRef.Dispose(); arrayRef = _pool.Get(7); Assert.AreEqual(8, arrayRef.Get().Length); Assert.AreNotSame(smallArray, arrayRef.Get()); }
/// <summary> /// Writes <code>count</code> bytes from the byte array /// <code>buffer</code> starting at position <code>offset</code> /// to this stream. /// The underlying stream MUST be valid. /// </summary> /// <param name="buffer"> /// The source buffer to read from. /// </param> /// <param name="offset"> /// The start position in <code>buffer</code> from where to /// get bytes. /// </param> /// <param name="count"> /// The number of bytes from <code>buffer</code> to write to /// this stream. /// </param> /// <exception cref="IOException"> /// If an error occurs while writing to this stream. /// </exception> /// <exception cref="ArgumentOutOfRangeException"> /// if <code>offset < 0</code> or <code>count < 0</code>, /// or if <code>offset + count</code> is bigger than the length /// of <code>buffer</code>. /// </exception> /// <exception cref="InvalidStreamException"> /// If the stream is invalid. /// </exception> public override void Write(byte[] buffer, int offset, int count) { if (offset < 0 || count < 0 || offset + count > buffer.Length) { throw new ArgumentOutOfRangeException( $"length={ buffer.Length }; regionStart={ offset }; regionLength={ count }"); } EnsureValid(); Realloc(_count + count); _bufRef.Get().Write(_count, buffer, offset, count); _count += count; }
/// <summary> /// Instantiates the <see cref="NativePooledByteBuffer"/>. /// </summary> public NativePooledByteBuffer(CloseableReference <NativeMemoryChunk> bufRef, int size) { Preconditions.CheckNotNull(bufRef); Preconditions.CheckArgument(size >= 0 && size <= bufRef.Get().Size); _bufRef = bufRef.Clone(); _size = size; }
public void TestTrimUnsuccessful() { CloseableReference <byte[]> arrayRef = _pool.Get(7); _delegatePool.Trim(MemoryTrimType.OnCloseToDalvikHeapLimit); Assert.IsNotNull(arrayRef.Get()); }
public void TestGet() { CloseableReference <byte[]> arrayRef = _pool.Get(1); Assert.AreEqual(0, _delegatePool._freeCounter.NumBytes); Assert.AreEqual(MIN_BUFFER_SIZE, arrayRef.Get().Length); }
public void TestConvert() { CloseableReference <SoftwareBitmap> reference = _closeableStaticBitmap.ConvertToBitmapReference(); Assert.AreSame(reference.Get(), _bitmap); Assert.IsTrue(_closeableStaticBitmap.IsClosed); }
public void TestTrimUnsuccessful() { CloseableReference <byte[]> arrayRef = _array.Get(7); _array.Trim(MemoryTrimType.OnCloseToDalvikHeapLimit); Assert.AreSame(arrayRef.Get(), _array._byteArraySoftRef.Get()); Assert.AreEqual(0, _array._semaphore.CurrentCount); }
public void TestAddBitmapReference() { using (CloseableReference <SoftwareBitmap> bitmapReference = _platformBitmapFactory.CreateBitmap(50, 50)) { Assert.IsNotNull(bitmapReference); Assert.AreEqual(1, _platformBitmapFactory.AddBitmapReferenceCallCount); Assert.AreEqual(bitmapReference.Get(), _platformBitmapFactory.Bitmap); } }
/// <summary> /// Creates a new instance of a CloseableStaticBitmap from an existing /// CloseableReference. The CloseableStaticBitmap will hold a reference /// to the bitmap until it's closed. /// </summary> public CloseableStaticBitmap( CloseableReference <SoftwareBitmap> bitmapReference, IQualityInfo qualityInfo, int rotationAngle) { _bitmapReference = Preconditions.CheckNotNull(bitmapReference.CloneOrNull()); _bitmap = _bitmapReference.Get(); _qualityInfo = qualityInfo; _rotationAngle = rotationAngle; }
public void getSize(string uriString, IPromise promise) { if (string.IsNullOrEmpty(uriString)) { promise.Reject(ErrorInvalidUri, "Cannot get the size of an image for an empty URI."); return; } var uri = new Uri(uriString); var imagePipeline = ImagePipelineFactory.Instance.GetImagePipeline(); var request = ImageRequestBuilder.NewBuilderWithSource(uri).Build(); var dataSource = imagePipeline.FetchDecodedImage(request, null); var dataSubscriber = new BaseDataSubscriberImpl <CloseableReference <CloseableImage> >( response => { if (!response.IsFinished()) { return(Task.CompletedTask); } CloseableReference <CloseableImage> reference = response.GetResult(); if (reference != null) { try { CloseableImage image = reference.Get(); promise.Resolve(new JObject { { "width", image.Width }, { "height", image.Height }, }); } catch (Exception ex) { promise.Reject(ErrorGetSizeFailure, ex.Message); } finally { CloseableReference <CloseableImage> .CloseSafely(reference); } } else { promise.Reject(ErrorGetSizeFailure, Invariant($"Invalid URI '{uri}' provided.")); } return(Task.CompletedTask); }, response => { promise.Reject(ErrorGetSizeFailure, response.GetFailureCause()); }); dataSource.Subscribe(dataSubscriber, FBCore.Concurrency.CallerThreadExecutor.Instance); }
/// <summary> /// Creates a bitmap of the specified width and height. /// </summary> /// <param name="width">The width of the bitmap.</param> /// <param name="height">The height of the bitmap.</param> /// <param name="bitmapConfig"> /// The bitmap config used to create the bitmap. /// </param> /// <param name="callerContext"> /// The Tag to track who create the bitmap. /// </param> /// <returns>A reference to the bitmap.</returns> /// <exception cref="OutOfMemoryException"> /// If the bitmap cannot be allocated. /// </exception> public CloseableReference <SoftwareBitmap> CreateBitmap( int width, int height, BitmapPixelFormat bitmapConfig, object callerContext) { CloseableReference <SoftwareBitmap> reference = CreateBitmapInternal(width, height, bitmapConfig); AddBitmapReference(reference.Get(), callerContext); return(reference); }
private void DoPostprocessing( CloseableReference <CloseableImage> sourceImageRef, bool isLast) { Preconditions.CheckArgument(CloseableReference <CloseableImage> .IsValid(sourceImageRef)); if (!ShouldPostprocess(sourceImageRef.Get())) { MaybeNotifyOnNewResult(sourceImageRef, isLast); return; } _listener.OnProducerStart(_requestId, NAME); CloseableReference <CloseableImage> destImageRef = null; try { try { destImageRef = PostprocessInternal(sourceImageRef.Get()); } catch (Exception e) { _listener.OnProducerFinishWithFailure( _requestId, NAME, e, GetExtraMap(_listener, _requestId, _postprocessor)); MaybeNotifyOnFailure(e); return; } _listener.OnProducerFinishWithSuccess( _requestId, NAME, GetExtraMap(_listener, _requestId, _postprocessor)); MaybeNotifyOnNewResult(destImageRef, isLast); } finally { CloseableReference <CloseableImage> .CloseSafely(destImageRef); } }
private void AssertReferencesSame <T>( string errorMessage, CloseableReference <T> expectedRef, CloseableReference <T> actualRef) { if (expectedRef == null) { Assert.IsNull(actualRef, errorMessage); } else { Assert.AreSame(expectedRef.Get(), actualRef.Get(), errorMessage); } }
/// <summary> /// Clients should override this method if the post-processing cannot be /// done in place. If the post-processing can be done in place, clients /// should override the /// Process(byte[], int, int, BitmapPixelFormat, BitmapAlphaMode) method. /// /// <para />The provided destination bitmap is of the same size as the /// source bitmap. There are no guarantees on the initial content of the /// destination bitmap, so the implementation has to make sure that it /// properly populates it. /// /// <para />The source bitmap must not be modified as it may be shared /// by the other clients. The implementation must use the provided /// destination bitmap as its output. /// </summary> /// <param name="destBitmap"> /// The destination bitmap to be used as output. /// </param> /// <param name="sourceBitmap"> /// The source bitmap to be used as input. /// </param> /// <param name="flexByteArrayPool"> /// The memory pool used for post process. /// </param> public unsafe virtual void Process( SoftwareBitmap destBitmap, SoftwareBitmap sourceBitmap, FlexByteArrayPool flexByteArrayPool) { Preconditions.CheckArgument(sourceBitmap.BitmapPixelFormat == destBitmap.BitmapPixelFormat); Preconditions.CheckArgument(!destBitmap.IsReadOnly); Preconditions.CheckArgument(destBitmap.PixelWidth == sourceBitmap.PixelWidth); Preconditions.CheckArgument(destBitmap.PixelHeight == sourceBitmap.PixelHeight); sourceBitmap.CopyTo(destBitmap); using (var buffer = destBitmap.LockBuffer(BitmapBufferAccessMode.Write)) using (var reference = buffer.CreateReference()) { // Get input data byte *srcData; uint capacity; ((IMemoryBufferByteAccess)reference).GetBuffer(out srcData, out capacity); // Allocate temp buffer for processing byte[] desData = default(byte[]); CloseableReference <byte[]> bytesArrayRef = default(CloseableReference <byte[]>); try { bytesArrayRef = flexByteArrayPool.Get((int)capacity); desData = bytesArrayRef.Get(); } catch (Exception) { // Allocates the byte array since the pool couldn't provide one desData = new byte[capacity]; } try { // Process output data Marshal.Copy((IntPtr)srcData, desData, 0, (int)capacity); Process(desData, destBitmap.PixelWidth, destBitmap.PixelHeight, destBitmap.BitmapPixelFormat, destBitmap.BitmapAlphaMode); Marshal.Copy(desData, 0, (IntPtr)srcData, (int)capacity); } finally { CloseableReference <byte[]> .CloseSafely(bytesArrayRef); } } }
public void TestFetchDecodedImageSuccess() { var completion = new ManualResetEvent(false); var dataSource = _imagePipeline.FetchDecodedImage(ImageRequest.FromUri(IMAGE_URL), null); var dataSubscriber = new BaseDataSubscriberImpl <CloseableReference <CloseableImage> >( async response => { CloseableReference <CloseableImage> reference = response.GetResult(); if (reference != null) { SoftwareBitmap bitmap = ((CloseableBitmap)reference.Get()).UnderlyingBitmap; try { Assert.IsTrue(bitmap.PixelWidth != 0); Assert.IsTrue(bitmap.PixelHeight != 0); Assert.IsTrue(_imagePipeline.IsInBitmapMemoryCache(ImageRequest.FromUri(IMAGE_URL))); Assert.IsTrue(await _imagePipeline.IsInDiskCacheAsync(IMAGE_URL).ConfigureAwait(false)); } catch (Exception) { Assert.Fail(); } finally { CloseableReference <CloseableImage> .CloseSafely(reference); completion.Set(); } } else { Assert.Fail(); completion.Set(); } }, response => { Assert.Fail(); completion.Set(); }); dataSource.Subscribe(dataSubscriber, CallerThreadExecutor.Instance); completion.WaitOne(); }
public void TestFetchEncodedImageSuccess() { var completion = new ManualResetEvent(false); var dataSource = _imagePipeline.FetchEncodedImage(ImageRequest.FromUri(IMAGE_URL), null); var dataSubscriber = new BaseDataSubscriberImpl <CloseableReference <IPooledByteBuffer> >( async response => { CloseableReference <IPooledByteBuffer> reference = response.GetResult(); if (reference != null) { IPooledByteBuffer inputStream = reference.Get(); try { Assert.IsTrue(inputStream.Size != 0); Assert.IsTrue(await _imagePipeline.IsInDiskCacheAsync(IMAGE_URL).ConfigureAwait(false)); } catch (Exception) { Assert.Fail(); } finally { CloseableReference <IPooledByteBuffer> .CloseSafely(reference); completion.Set(); } } else { Assert.Fail(); completion.Set(); } }, response => { Assert.Fail(); completion.Set(); }); dataSource.Subscribe(dataSubscriber, CallerThreadExecutor.Instance); completion.WaitOne(); }
/// <summary> /// Removes key-value from the StagingArea. Both key and value must match. /// </summary> /// <param name="key">The cache key.</param> /// <param name="encodedImage">Value corresponding to key.</param> /// <returns>true if item was removed.</returns> public bool Remove(ICacheKey key, EncodedImage encodedImage) { lock (_mapGate) { Preconditions.CheckNotNull(key); Preconditions.CheckNotNull(encodedImage); Preconditions.CheckArgument(EncodedImage.IsValid(encodedImage)); EncodedImage oldValue = default(EncodedImage); if (!_map.TryGetValue(key, out oldValue)) { return(false); } CloseableReference <IPooledByteBuffer> oldReference = oldValue.GetByteBufferRef(); CloseableReference <IPooledByteBuffer> reference = encodedImage.GetByteBufferRef(); try { if (oldReference == null || reference == null || oldReference.Get() != reference.Get()) { return(false); } _map.Remove(key); } finally { CloseableReference <IPooledByteBuffer> .CloseSafely(reference); CloseableReference <IPooledByteBuffer> .CloseSafely(oldReference); EncodedImage.CloseSafely(oldValue); } #if DEBUG_STAGING_AREA LogStats(); #endif // DEBUG_STAGING_AREA return(true); } }
/// <summary> /// Returns true if the image is a JPEG and its data is already complete /// at the specified length, false otherwise. /// </summary> public bool IsCompleteAt(int length) { if (Format != ImageFormat.JPEG) { return(true); } // If the image is backed by FileStream return true since they will // always be complete. if (_inputStreamSupplier != null) { return(true); } // The image should be backed by a ByteBuffer Preconditions.CheckNotNull(_pooledByteBufferRef); IPooledByteBuffer buf = _pooledByteBufferRef.Get(); return((buf.Read(length - 2) == JfifUtil.MARKER_FIRST_BYTE) && (buf.Read(length - 1) == JfifUtil.MARKER_EOI)); }
/// <summary> /// Clients should override this method only if the post-processed /// bitmap has to be of a different size than the source bitmap. /// If the post-processed bitmap is of the same size, clients should /// override one of the other two methods. /// /// <para />The source bitmap must not be modified as it may be shared /// by the other clients. The implementation must create a new bitmap /// that is safe to be modified and return a reference to it. /// Clients should use <code>bitmapFactory</code> to create a new bitmap. /// </summary> /// <param name="sourceBitmap">The source bitmap.</param> /// <param name="bitmapFactory"> /// The factory to create a destination bitmap. /// </param> /// <param name="flexByteArrayPool"> /// The memory pool used for post process. /// </param> /// <returns> /// A reference to the newly created bitmap. /// </returns> public CloseableReference <SoftwareBitmap> Process( SoftwareBitmap sourceBitmap, PlatformBitmapFactory bitmapFactory, FlexByteArrayPool flexByteArrayPool) { CloseableReference <SoftwareBitmap> destBitmapRef = bitmapFactory.CreateBitmapInternal( sourceBitmap.PixelWidth, sourceBitmap.PixelHeight, sourceBitmap.BitmapPixelFormat); try { Process(destBitmapRef.Get(), sourceBitmap, flexByteArrayPool); return(CloseableReference <SoftwareBitmap> .CloneOrNull(destBitmapRef)); } finally { CloseableReference <SoftwareBitmap> .CloseSafely(destBitmapRef); } }
/// <summary> /// Caches the given key-value pair. /// /// <para />Important: the client should use the returned reference /// instead of the original one. It is the caller's responsibility /// to close the returned reference once not needed anymore. /// </summary> /// <returns> /// The new reference to be used, null if the value cannot be cached. /// </returns> public CloseableReference <V> Cache( K key, CloseableReference <V> valueRef, IEntryStateObserver <K> observer) { Preconditions.CheckNotNull(key); Preconditions.CheckNotNull(valueRef); MaybeUpdateCacheParams(); Entry oldExclusive; CloseableReference <V> oldRefToClose = null; CloseableReference <V> clientRef = null; lock (_cacheGate) { // Remove the old item (if any) as it is stale now oldExclusive = _exclusiveEntries.Remove(key); Entry oldEntry = _cachedEntries.Remove(key); if (oldEntry != null) { MakeOrphan(oldEntry); oldRefToClose = ReferenceToClose(oldEntry); } if (CanCacheNewValue(valueRef.Get())) { Entry newEntry = Entry.of(key, valueRef, observer); _cachedEntries.Put(key, newEntry); clientRef = NewClientReference(newEntry); } } CloseableReference <V> .CloseSafely(oldRefToClose); MaybeNotifyExclusiveEntryRemoval(oldExclusive); MaybeEvictEntries(); return(clientRef); }
/// <summary> /// Returns a stream from the internal stream supplier if it's not null. /// Otherwise returns an stream for the internal buffer reference if /// valid and null otherwise. /// /// <para />The caller has to close the stream after using it. /// </summary> public Stream GetInputStream() { if (_inputStreamSupplier != null) { return(_inputStreamSupplier.Get()); } CloseableReference <IPooledByteBuffer> pooledByteBufferRef = CloseableReference <IPooledByteBuffer> .CloneOrNull(_pooledByteBufferRef); if (pooledByteBufferRef != null) { try { return(new PooledByteBufferInputStream(pooledByteBufferRef.Get())); } finally { CloseableReference <IPooledByteBuffer> .CloseSafely(pooledByteBufferRef); } } return(null); }
/// <summary> /// Fetches the encoded BitmapImage. /// </summary> /// <param name="uri">The image uri.</param> /// <param name="token">The cancellation token.</param> /// <param name="dispatcher"> /// The current view's dispatcher, used to create BitmapImage. /// </param> /// <returns>The encoded BitmapImage.</returns> /// <exception cref="IOException"> /// If the image uri can't be found. /// </exception> public Task <BitmapImage> FetchEncodedBitmapImageAsync( Uri uri, CancellationToken token = default(CancellationToken), CoreDispatcher dispatcher = null) { var taskCompletionSource = new TaskCompletionSource <BitmapImage>(); var dataSource = FetchEncodedImage(ImageRequest.FromUri(uri), null); var dataSubscriber = new BaseDataSubscriberImpl <CloseableReference <IPooledByteBuffer> >( async response => { CloseableReference <IPooledByteBuffer> reference = response.GetResult(); if (reference != null) { //---------------------------------------------------------------------- // Phong Cao: InMemoryRandomAccessStream can't write anything < 16KB. // http://stackoverflow.com/questions/25928408/inmemoryrandomaccessstream-incorrect-behavior //---------------------------------------------------------------------- IPooledByteBuffer inputStream = reference.Get(); int supportedSize = Math.Max(16 * ByteConstants.KB, inputStream.Size); // Allocate temp buffer for stream convert byte[] bytesArray = default(byte[]); CloseableReference <byte[]> bytesArrayRef = default(CloseableReference <byte[]>); try { bytesArrayRef = _flexByteArrayPool.Get(supportedSize); bytesArray = bytesArrayRef.Get(); } catch (Exception) { // Allocates the byte array since the pool couldn't provide one bytesArray = new byte[supportedSize]; } try { inputStream.Read(0, bytesArray, 0, inputStream.Size); await DispatcherHelpers.CallOnDispatcherAsync(async() => { using (var outStream = new InMemoryRandomAccessStream()) using (var writeStream = outStream.AsStreamForWrite()) { await writeStream.WriteAsync(bytesArray, 0, supportedSize); outStream.Seek(0); BitmapImage bitmapImage = new BitmapImage(); await bitmapImage.SetSourceAsync(outStream).AsTask().ConfigureAwait(false); taskCompletionSource.SetResult(bitmapImage); } }, dispatcher).ConfigureAwait(false); } catch (Exception e) { taskCompletionSource.SetException(e); } finally { CloseableReference <IPooledByteBuffer> .CloseSafely(reference); CloseableReference <byte[]> .CloseSafely(bytesArrayRef); } } else { taskCompletionSource.SetResult(null); } }, response => { taskCompletionSource.SetException(response.GetFailureCause()); }); dataSource.Subscribe(dataSubscriber, _handleResultExecutor); token.Register(() => { dataSource.Close(); taskCompletionSource.TrySetCanceled(); }); return(taskCompletionSource.Task); }
/// <summary> /// Start producing results for given context. /// Provided consumer is notified whenever progress is made /// (new value is ready or error occurs). /// </summary> public void ProduceResults( IConsumer <CloseableReference <CloseableImage> > consumer, IProducerContext producerContext) { IProducerListener listener = producerContext.Listener; string requestId = producerContext.Id; listener.OnProducerStart(requestId, ProducerName); ImageRequest imageRequest = producerContext.ImageRequest; object callerContext = producerContext.CallerContext; ICacheKey cacheKey = _cacheKeyFactory.GetBitmapCacheKey(imageRequest, callerContext); IDictionary <string, string> extraMap = default(IDictionary <string, string>); CloseableReference <CloseableImage> cachedReference = _memoryCache.Get(cacheKey); if (cachedReference != null) { bool isFinal = cachedReference.Get().QualityInfo.IsOfFullQuality; if (isFinal) { extraMap = new Dictionary <string, string>() { { VALUE_FOUND, "true" } }; listener.OnProducerFinishWithSuccess( requestId, ProducerName, listener.RequiresExtraMap(requestId) ? new ReadOnlyDictionary <string, string>(extraMap) : null); consumer.OnProgressUpdate(1f); } consumer.OnNewResult(cachedReference, isFinal); cachedReference.Dispose(); if (isFinal) { return; } } if (producerContext.LowestPermittedRequestLevel >= RequestLevel.BITMAP_MEMORY_CACHE) { extraMap = new Dictionary <string, string>() { { VALUE_FOUND, "false" } }; listener.OnProducerFinishWithSuccess( requestId, ProducerName, listener.RequiresExtraMap(requestId) ? new ReadOnlyDictionary <string, string>(extraMap) : null); consumer.OnNewResult(null, true); return; } extraMap = new Dictionary <string, string>() { { VALUE_FOUND, "false" } }; IConsumer <CloseableReference <CloseableImage> > wrappedConsumer = WrapConsumer(consumer, cacheKey); listener.OnProducerFinishWithSuccess( requestId, ProducerName, listener.RequiresExtraMap(requestId) ? new ReadOnlyDictionary <string, string>(extraMap) : null); _inputProducer.ProduceResults(wrappedConsumer, producerContext); }
protected override void OnNewResultImpl( CloseableReference <CloseableImage> newResult, bool isLast) { // Ignore invalid intermediate results and forward the null result if last if (newResult == null) { if (isLast) { Consumer.OnNewResult(null, true); } return; } // Stateful results cannot be cached and are just forwarded if (newResult.Get().Stateful) { Consumer.OnNewResult(newResult, isLast); return; } // If the intermediate result is not of a better quality than the cached // result, forward the already cached result and don't cache the new result. if (!isLast) { CloseableReference <CloseableImage> currentCachedResult = _memoryCache.Get(_cacheKey); if (currentCachedResult != null) { try { IQualityInfo newInfo = newResult.Get().QualityInfo; IQualityInfo cachedInfo = currentCachedResult.Get().QualityInfo; if (cachedInfo.IsOfFullQuality || cachedInfo.Quality >= newInfo.Quality) { Consumer.OnNewResult(currentCachedResult, false); return; } } finally { CloseableReference <CloseableImage> .CloseSafely(currentCachedResult); } } } // Cache and forward the new result CloseableReference <CloseableImage> newCachedResult = _memoryCache.Cache(_cacheKey, newResult); try { if (isLast) { Consumer.OnProgressUpdate(1f); } Consumer.OnNewResult( (newCachedResult != null) ? newCachedResult : newResult, isLast); } finally { CloseableReference <CloseableImage> .CloseSafely(newCachedResult); } }