/// <summary> /// Indicates if the task uses the same native control /// </summary> /// <returns><c>true</c>, if same native control is used, <c>false</c> otherwise.</returns> /// <param name="task">Task to check.</param> public override bool UsesSameNativeControl(IImageLoaderTask task) { var loaderTask = task as ImageLoaderTask; if (loaderTask == null) return false; return UsesSameNativeControl(loaderTask); }
public void CancelLoading() { if (ImageLoaderTask != null) { ImageService.Instance.CancelWorkFor(ImageLoaderTask); ImageLoaderTask = null; } }
public virtual void Cancel(IImageLoaderTask task) { try { if (task != null && !task.IsCancelled && !task.IsCompleted) { task.Cancel(); } } catch (Exception e) { Logger.Error(string.Format("Cancelling task failed: {0}", task?.Key), e); } finally { task?.Dispose(); EvictStaleTasks(); } }
/// <summary> /// Cancels any pending work for the task. /// </summary> /// <param name="task">Image loading task to cancel</param> /// <returns><c>true</c> if this instance cancel task; otherwise, <c>false</c>.</returns> public void Cancel(IImageLoaderTask task) { try { if (task != null && !task.IsCancelled && !task.Completed) { task.Cancel(); } } catch (Exception e) { _logger.Error("Exception occurent trying to cancel the task", e); } finally { if (task != null && task.IsCancelled) task.Parameters.Dispose(); // this will ensure we don't keep a reference due to callbacks } }
public virtual async void LoadImage(IImageLoaderTask task) { Interlocked.Increment(ref _loadCount); EvictStaleTasks(); if (Configuration.VerbosePerformanceLogging && (_loadCount % 10) == 0) { LogSchedulerStats(); } if (task?.Parameters?.Source != ImageSource.Stream && string.IsNullOrWhiteSpace(task?.Parameters?.Path)) { Logger.Error("ImageService: null path ignored"); task?.Dispose(); return; } if (task == null) return; if (task.IsCancelled || task.IsCompleted || ExitTasksEarly) { task?.Dispose(); return; } if (task.Parameters.DelayInMs != null && task.Parameters.DelayInMs > 0) { await Task.Delay(task.Parameters.DelayInMs.Value).ConfigureAwait(false); } // If we have the image in memory then it's pointless to schedule the job: just display it straight away if (task.CanUseMemoryCache) { if (await task.TryLoadFromMemoryCacheAsync().ConfigureAwait(false)) { Interlocked.Increment(ref _statsTotalMemoryCacheHits); task?.Dispose(); return; } } _dispatch = _dispatch.ContinueWith(t => { try { QueueImageLoadingTask(task); } catch (Exception ex) { Logger.Error(string.Format("Image loaded failed: {0}", task?.Key), ex); } }); }
protected Task CreateFrameworkTask(IImageLoaderTask imageLoadingTask) { var parameters = imageLoadingTask.Parameters; var tcs = new TaskCompletionSource<bool>(); var successCallback = parameters.OnSuccess; parameters.Success((size, result) => { tcs.TrySetResult(true); successCallback?.Invoke(size, result); }); var finishCallback = parameters.OnFinish; parameters.Finish(sw => { tcs.TrySetResult(false); finishCallback?.Invoke(sw); }); return tcs.Task; }
/// <summary> /// Queue an image loading task. /// </summary> /// <param name="task">Image loading task.</param> public static void LoadImage(IImageLoaderTask task) { Scheduler.LoadImage(task); }
/// <summary> /// Cancel any loading work for the given ImageView /// </summary> /// <param name="task">Image loading task to cancel.</param> public static void CancelWorkFor(IImageLoaderTask task) { Scheduler.Cancel(task); }
private void QueueAndGenerateImage(IImageLoaderTask task) { _logger.Debug(string.Format("Generating/retrieving image: {0}", task.GetKey())); int position = Interlocked.Increment(ref _currentPosition); var currentPendingTask = new PendingTask() { Position = position, ImageLoadingTask = task, FrameworkWrappingTask = CreateFrameworkTask(task) }; PendingTask alreadyRunningTaskForSameKey = null; lock (_pendingTasksLock) { alreadyRunningTaskForSameKey = FindSimilarPendingTask(task); if (alreadyRunningTaskForSameKey == null) { Interlocked.Increment(ref _statsTotalPending); _pendingTasks.Add(currentPendingTask); } else { alreadyRunningTaskForSameKey.Position = position; } } if (alreadyRunningTaskForSameKey == null || !currentPendingTask.ImageLoadingTask.CanUseMemoryCache()) { Run(currentPendingTask); } else { WaitForSimilarTask(currentPendingTask, alreadyRunningTaskForSameKey); } }
/// <summary> /// Queue an image loading task. /// </summary> /// <param name="task">Image loading task.</param> public static void LoadImage(IImageLoaderTask task) { Scheduler.LoadImage(task); AddRequestToHistory(task); }
public virtual bool IsTaskValid(IImageLoaderTask task) => IsValid;
public override bool IsTaskValid(IImageLoaderTask task) { return(IsValid); }
private void Enqueue(IImageLoaderTask task) { PendingTasks.Enqueue(task, task.Parameters.Priority ?? GetDefaultPriority(task.Parameters.Source)); }
protected async Task TakeFromPendingTasksAndRunAsync() { if (PendingTasks.Count == 0) { return; } Dictionary <string, IImageLoaderTask> tasksToRun = null; var preloadOrUrlTasksCount = 0; var urlTasksCount = 0; var preloadTasksCount = 0; lock (_lock) { if (RunningTasks.Count >= MaxParallelTasks) { urlTasksCount = RunningTasks.Count(v => v.Value != null && !v.Value.Parameters.Preload && v.Value.Parameters.Source == ImageSource.Url); preloadTasksCount = RunningTasks.Count(v => v.Value != null && v.Value.Parameters.Preload); preloadOrUrlTasksCount = preloadTasksCount + urlTasksCount; if (preloadOrUrlTasksCount == 0 || preloadOrUrlTasksCount != MaxParallelTasks) { return; } // Allow only half of MaxParallelTasks as additional allowed tasks when preloading occurs to prevent starvation if (RunningTasks.Count - Math.Max(1, Math.Min(preloadOrUrlTasksCount, MaxParallelTasks / 2)) >= MaxParallelTasks) { return; } } var numberOfTasks = MaxParallelTasks - RunningTasks.Count + Math.Min(preloadOrUrlTasksCount, MaxParallelTasks / 2); tasksToRun = new Dictionary <string, IImageLoaderTask>(); IImageLoaderTask task = null; while (tasksToRun.Count < numberOfTasks && PendingTasks.TryDequeue(out task)) { if (task == null || task.IsCancelled || task.IsCompleted) { continue; } // We don't want to load, at the same time, images that have same key or same raw key at the same time // This way we prevent concurrent downloads and benefit from caches var rawKey = task.KeyRaw; if (RunningTasks.ContainsKey(rawKey) || tasksToRun.ContainsKey(rawKey)) { SimilarTasks.Add(task); continue; } if (preloadOrUrlTasksCount != 0) { if (!task.Parameters.Preload && (urlTasksCount == 0 || task.Parameters.Source != ImageSource.Url)) { tasksToRun.Add(rawKey, task); } else { Enqueue(task); break; } } else { tasksToRun.Add(rawKey, task); } } foreach (var item in tasksToRun) { RunningTasks.Add(item.Key, item.Value); Interlocked.Increment(ref _statsTotalRunning); } } if (tasksToRun != null && tasksToRun.Count > 0) { var tasks = tasksToRun.Select(async p => { await Task.Factory.StartNew(async() => { try { await RunImageLoadingTaskAsync(p.Value).ConfigureAwait(false); } catch (Exception ex) { Logger.Error("TakeFromPendingTasksAndRun exception", ex); } }, CancellationToken.None, TaskCreationOptions.PreferFairness | TaskCreationOptions.DenyChildAttach | TaskCreationOptions.HideScheduler, TaskScheduler.Default).ConfigureAwait(false); }); await Task.WhenAll(tasks).ConfigureAwait(false); } }
public virtual bool UsesSameNativeControl(IImageLoaderTask anotherTask) { return(Target.UsesSameNativeControl(anotherTask)); }
public abstract bool UsesSameNativeControl(IImageLoaderTask task);
public override bool IsTaskValid(IImageLoaderTask task) { var controlTask = Control?.GetImageLoaderTask(); return(IsValid && (controlTask == null || controlTask == task)); }
protected abstract void SetTaskForTarget(IImageLoaderTask currentTask);
public virtual bool UsesSameNativeControl(IImageLoaderTask task) => false;
private void QueueAndGenerateImage(IImageLoaderTask task) { _logger.Debug(string.Format("Generating/retrieving image: {0}", task.GetKey())); var currentPendingTask = new PendingTask() { ImageLoadingTask = task }; PendingTask alreadyRunningTaskForSameKey = null; lock (_pauseWorkLock) { lock (_pendingTasksLock) { alreadyRunningTaskForSameKey = _pendingTasks.FirstOrDefault(t => t.ImageLoadingTask.GetKey() == task.GetKey() && (!t.ImageLoadingTask.IsCancelled)); if (alreadyRunningTaskForSameKey == null) _pendingTasks.Add(currentPendingTask); } } if (alreadyRunningTaskForSameKey == null || !currentPendingTask.ImageLoadingTask.CanUseMemoryCache()) { Run(currentPendingTask); } else { WaitForSimilarTask(currentPendingTask, alreadyRunningTaskForSameKey); } }
public virtual void SetAsEmpty(IImageLoaderTask task) { }
private PendingTask FindSimilarPendingTask(IImageLoaderTask task) { // At first check if the exact same items exists in pending tasks (exact same means same transformations, same downsample, ...) // Since it will be exactly the same it can be retrieved from memory cache string key = task.GetKey(raw: false); var alreadyRunningTaskForSameKey = _pendingTasks.FirstOrDefault(t => t.ImageLoadingTask.GetKey(raw: false) == key); return alreadyRunningTaskForSameKey; }
public virtual void Set(IImageLoaderTask task, TImageContainer image, bool animated) { }
/// <summary> /// Queue an image loading task. /// </summary> /// <param name="task">Image loading task.</param> public void LoadImage(IImageLoaderTask task) { if (task == null) return; if (task.Parameters.DelayInMs == null && Config.DelayInMs > 0) task.Parameters.Delay(Config.DelayInMs); Scheduler.LoadImage(task); }
public virtual void SetImageLoadingTask(IImageLoaderTask task) { }
private static void AddRequestToHistory(IImageLoaderTask task) { AddRequestToHistory(task.Parameters.Path, task.GetKey()); AddRequestToHistory(task.Parameters.CustomCacheKey, task.GetKey()); AddRequestToHistory(task.Parameters.LoadingPlaceholderPath, task.GetKey(task.Parameters.LoadingPlaceholderPath)); AddRequestToHistory(task.Parameters.ErrorPlaceholderPath, task.GetKey(task.Parameters.ErrorPlaceholderPath)); }
/// <summary> /// Removes a pending image loading task from the work queue. /// </summary> /// <param name="task">Image loading task to remove.</param> public void RemovePendingTask(IImageLoaderTask task) { Scheduler.RemovePendingTask(task); }
/// <summary> /// Removes a pending image loading task from the work queue. /// </summary> /// <param name="task">Image loading task to remove.</param> public static void RemovePendingTask(IImageLoaderTask task) { Scheduler.RemovePendingTask(task); }
protected async Task TakeFromPendingTasksAndRunAsync() { if (PendingTasks.Count == 0) { return; } Dictionary <string, IImageLoaderTask> tasksToRun = null; int preloadOrUrlTasksCount = 0; int urlTasksCount = 0; int preloadTasksCount = 0; lock (_lock) { if (RunningTasks.Count >= MaxParallelTasks) { urlTasksCount = RunningTasks.Count(v => v.Value != null && (!v.Value.Parameters.Preload && v.Value.Parameters.Source == ImageSource.Url)); preloadTasksCount = RunningTasks.Count(v => v.Value != null && v.Value.Parameters.Preload); preloadOrUrlTasksCount = preloadTasksCount + urlTasksCount; if (preloadOrUrlTasksCount == 0 || preloadOrUrlTasksCount != MaxParallelTasks) { return; } // Allow only half of MaxParallelTasks as additional allowed tasks when preloading occurs to prevent starvation if (RunningTasks.Count - Math.Max(1, Math.Min(preloadOrUrlTasksCount, MaxParallelTasks / 2)) >= MaxParallelTasks) { return; } } int numberOfTasks = MaxParallelTasks - RunningTasks.Count + Math.Min(preloadOrUrlTasksCount, MaxParallelTasks / 2); tasksToRun = new Dictionary <string, IImageLoaderTask>(); IImageLoaderTask task = null; while (tasksToRun.Count < numberOfTasks && PendingTasks.TryDequeue(out task)) { if (task == null || task.IsCancelled || task.IsCompleted) { continue; } // We don't want to load, at the same time, images that have same key or same raw key at the same time // This way we prevent concurrent downloads and benefit from caches string rawKey = task.KeyRaw; if (RunningTasks.ContainsKey(rawKey) || tasksToRun.ContainsKey(rawKey)) { SimilarTasks.Add(task); continue; } if (preloadOrUrlTasksCount != 0) { if (!task.Parameters.Preload && (urlTasksCount == 0 || task.Parameters.Source != ImageSource.Url)) { tasksToRun.Add(rawKey, task); } else { Enqueue(task); break; } } else { tasksToRun.Add(rawKey, task); } } foreach (var item in tasksToRun) { RunningTasks.Add(item.Key, item.Value); Interlocked.Increment(ref _statsTotalRunning); } } if (tasksToRun != null && tasksToRun.Count > 0) { var tasks = tasksToRun.Select(p => RunImageLoadingTaskAsync(p.Value)); await Task.WhenAll(tasks).ConfigureAwait(false); } }
public virtual void RemovePendingTask(IImageLoaderTask task) { lock (_pendingTasksLock) { PendingTasks.RemoveAll(p => p.ImageLoadingTask == task); } }
public override void Set(IImageLoaderTask task, NSImage image, bool animated) { if (task == null || task.IsCancelled) { return; } var control = Control; if (control == null) { return; } bool isXamarinForms = control.Layer is FFCALayer; var parameters = task.Parameters; var representations = image.Representations(); if (representations.Length > 1) { control.Layer.Contents = null; control.Image = image; control.Animates = true; control.SetNeedsDisplay(); control.CanDrawSubviewsIntoLayer = true; if (IsLayoutNeeded(task)) { control.NeedsLayout = true; } } else { if (animated) { //TODO fade animation if (isXamarinForms) { control.Layer.Contents = image.CGImage; } else { control.Image = image; } control.SetNeedsDisplay(); if (IsLayoutNeeded(task)) { control.NeedsLayout = true; } } else { if (isXamarinForms) { control.Layer.Contents = image.CGImage; } else { control.Image = image; } control.SetNeedsDisplay(); if (IsLayoutNeeded(task)) { control.NeedsLayout = true; } } } }
protected void QueueImageLoadingTask(IImageLoaderTask task) { int position = Interlocked.Increment(ref _currentPosition); var currentPendingTask = new PendingTask() { Position = position, ImageLoadingTask = task, FrameworkWrappingTask = CreateFrameworkTask(task) }; if (task.IsCancelled || task.IsCompleted || ExitTasksEarly) { task?.Dispose(); return; } PendingTask similarRunningTask = null; lock (_pendingTasksLock) { if (!task.Parameters.Preload) { foreach (var pendingTask in PendingTasks.ToList()) // FMT: here we need a copy since cancelling will trigger them to be removed, hence collection is modified during enumeration { if (pendingTask.ImageLoadingTask != null && pendingTask.ImageLoadingTask.UsesSameNativeControl(task)) pendingTask.ImageLoadingTask.CancelIfNeeded(); } EvictStaleTasks(); } similarRunningTask = PendingTasks.FirstOrDefault(t => t.ImageLoadingTask.Key == task.Key); if (similarRunningTask == null) { Interlocked.Increment(ref _statsTotalPending); PendingTasks.Add(currentPendingTask); } else { similarRunningTask.Position = position; } } if (PauseWork) return; if (similarRunningTask == null || !currentPendingTask.ImageLoadingTask.CanUseMemoryCache) { TakeFromPendingTasksAndRun(); } else { WaitForSimilarTaskFinished(currentPendingTask, similarRunningTask); } }
public void RemovePendingTask(IImageLoaderTask task) { lock (_pauseWorkLock) { lock (_pendingTasksLock) { var pendingTask = _pendingTasks.FirstOrDefault(t => t.ImageLoadingTask == task); if (pendingTask != null) _pendingTasks.Remove(pendingTask); } } }
public override bool UsesSameNativeControl(IImageLoaderTask task) { return(_target.UsesSameNativeControl((ImageLoaderTask)task)); }
public void CancelWorkFor(IImageLoaderTask task) { task?.Cancel(); }
public abstract bool UsesSameNativeControl(IImageLoaderTask task);
/// <summary> /// Cancel any loading work for the given ImageView /// </summary> /// <param name="task">Image loading task to cancel.</param> public static void CancelWorkFor(IImageLoaderTask task) { Scheduler.Cancel(task); }
/// <summary> /// Schedules the image loading. If image is found in cache then it returns it, otherwise it loads it. /// </summary> /// <param name="task">Image loading task.</param> public async void LoadImage(IImageLoaderTask task) { if (task == null) return; if (task.IsCancelled) { task.Parameters.Dispose(); // this will ensure we don't keep a reference due to callbacks return; } bool loadedFromCache = await task.PrepareAndTryLoadingFromCacheAsync().ConfigureAwait(false); if (loadedFromCache) { if (task.Parameters.OnFinish != null) task.Parameters.OnFinish(task); task.Parameters.Dispose(); // this will ensure we don't keep a reference due to callbacks return; // image successfully loaded from cache } if (task.IsCancelled || _pauseWork) { task.Parameters.Dispose(); // this will ensure we don't keep a reference due to callbacks return; } QueueAndGenerateImage(task); }
/// <summary> /// Indicates if the task uses the same native control /// </summary> /// <returns><c>true</c>, if same native control is used, <c>false</c> otherwise.</returns> /// <param name="task">Task to check.</param> public override bool UsesSameNativeControl(IImageLoaderTask task) { return _target.UsesSameNativeControl((ImageLoaderTask)task); }
/// <summary> /// Schedules the image loading. If image is found in cache then it returns it, otherwise it loads it. /// </summary> /// <param name="task">Image loading task.</param> public async void LoadImage(IImageLoaderTask task) { if (task == null) return; if (task.IsCancelled) { task.Parameters.Dispose(); // this will ensure we don't keep a reference due to callbacks return; } List<PendingTask> pendingTasksCopy; lock (_pendingTasksLock) { pendingTasksCopy = _pendingTasks.ToList(); } foreach (var pendingTask in pendingTasksCopy) { if (pendingTask.ImageLoadingTask != null && pendingTask.ImageLoadingTask.UsesSameNativeControl(task)) pendingTask.ImageLoadingTask.CancelIfNeeded(); } bool loadedFromCache = false; if (task.CanUseMemoryCache()) { loadedFromCache = await task.PrepareAndTryLoadingFromCacheAsync().ConfigureAwait(false); } if (loadedFromCache) { if (task.Parameters.OnFinish != null) task.Parameters.OnFinish(task); task.Dispose(); return; // image successfully loaded from cache } if (task.IsCancelled || _pauseWork) { task.Parameters.Dispose(); // this will ensure we don't keep a reference due to callbacks return; } QueueAndGenerateImage(task); }
/// <summary> /// Schedules the image loading. If image is found in cache then it returns it, otherwise it loads it. /// </summary> /// <param name="task">Image loading task.</param> public async void LoadImage(IImageLoaderTask task) { Interlocked.Increment(ref _loadCount); if (_verbosePerformanceLogging && (_loadCount % 10) == 0) { LogSchedulerStats(); } if (task == null) return; if (task.IsCancelled) { task.Parameters?.Dispose(); // this will ensure we don't keep a reference due to callbacks return; } if (task.Parameters.DelayInMs != null && task.Parameters.DelayInMs > 0) { await Task.Delay(task.Parameters.DelayInMs.Value).ConfigureAwait(false); } // If we have the image in memory then it's pointless to schedule the job: just display it straight away if (task.CanUseMemoryCache()) { var cacheResult = await task.TryLoadingFromCacheAsync().ConfigureAwait(false); if (cacheResult == CacheResult.Found) // If image is loaded from cache there is nothing to do here anymore { Interlocked.Increment(ref _statsTotalMemoryCacheHits); } if (cacheResult == CacheResult.Found || cacheResult == CacheResult.ErrorOccured) // if something weird happened with the cache... error callback has already been called, let's just leave { if (task.Parameters.OnFinish != null) task.Parameters.OnFinish(task); task.Dispose(); return; } } else if (task?.Parameters?.Source != ImageSource.Stream && string.IsNullOrWhiteSpace(task?.Parameters?.Path)) { _logger.Debug("ImageService: null path ignored"); return; } _dispatch = _dispatch.ContinueWith(async t => { try { await LoadImageAsync(task).ConfigureAwait(false); } catch (Exception ex) { _logger.Error("An error occured while loading image", ex); } }); }
private async Task LoadImageAsync(IImageLoaderTask task) { if (task.IsCancelled) { task.Parameters?.Dispose(); // this will ensure we don't keep a reference due to callbacks return; } if (!task.Parameters.Preload) { lock (_pendingTasksLock) { foreach (var pendingTask in _pendingTasks.ToList()) // FMT: here we need a copy since cancelling will trigger them to be removed, hence collection is modified during enumeration { if (pendingTask.ImageLoadingTask != null && pendingTask.ImageLoadingTask.UsesSameNativeControl(task)) pendingTask.ImageLoadingTask.CancelIfNeeded(); } } } bool loadedFromCache = await task.PrepareAndTryLoadingFromCacheAsync().ConfigureAwait(false); if (loadedFromCache) { if (task.Parameters.OnFinish != null) task.Parameters.OnFinish(task); task.Dispose(); return; // image successfully loaded from cache } if (task.IsCancelled || _pauseWork) { task.Parameters?.Dispose(); // this will ensure we don't keep a reference due to callbacks return; } QueueAndGenerateImage(task); }
/// <summary> /// Queue an image loading task. /// </summary> /// <param name="task">Image loading task.</param> public static void LoadImage(IImageLoaderTask task) { Scheduler.LoadImage(task); }