Skip to content

Commit 00e56ce

Browse files
committed
Rework AnimatedImage teardown, bitmap pooling and load queue
1 parent 9547592 commit 00e56ce

2 files changed

Lines changed: 432 additions & 22 deletions

File tree

‎Telegram/Common/ConcurrentQueueWorker.cs‎

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,82 @@ public void Run(Action task)
158158
}, this);
159159
}
160160
}
161+
162+
/// <summary>
163+
/// A stack of actions drained by up to <see cref="MaxConcurrency"/> threads at once, for work
164+
/// that is independent per item and too expensive to serialise - decoding a panel's worth of
165+
/// stickers, where one at a time turns a few milliseconds each into seconds of them arriving
166+
/// one by one.
167+
/// </summary>
168+
/// <remarks>
169+
/// A stack rather than a queue: the most recently pushed item is the one nearest what the user
170+
/// is looking at, so taking from the back follows a scroll instead of trailing it.
171+
///
172+
/// A failing action is logged and the drain continues. Abandoning the rest of the queue
173+
/// because one item threw would strand everything behind it.
174+
/// </remarks>
175+
public partial class ParallelActionWorker
176+
{
177+
private readonly ConcurrentStack<Action> taskQueue = new();
178+
private int _concurrentCount = 0;
179+
180+
public int MaxConcurrency { get; }
181+
182+
public ParallelActionWorker(int maxConcurrency)
183+
{
184+
MaxConcurrency = Math.Max(1, maxConcurrency);
185+
}
186+
187+
public void Run(Action task)
188+
{
189+
taskQueue.Push(task);
190+
TryStartDrain();
191+
}
192+
193+
private void TryStartDrain()
194+
{
195+
var count = Volatile.Read(ref _concurrentCount);
196+
197+
while (count < MaxConcurrency)
198+
{
199+
var previous = Interlocked.CompareExchange(ref _concurrentCount, count + 1, count);
200+
if (previous == count)
201+
{
202+
ThreadPool.UnsafeQueueUserWorkItem(static state => ((ParallelActionWorker)state!).Drain(), this);
203+
return;
204+
}
205+
206+
count = previous;
207+
}
208+
}
209+
210+
private void Drain()
211+
{
212+
try
213+
{
214+
while (taskQueue.TryPop(out var next))
215+
{
216+
try
217+
{
218+
next();
219+
}
220+
catch (Exception ex)
221+
{
222+
Logger.Error(ex);
223+
}
224+
}
225+
}
226+
finally
227+
{
228+
Interlocked.Decrement(ref _concurrentCount);
229+
230+
// A push that landed between the pop that failed and the decrement above would
231+
// otherwise sit in the stack with nobody left to drain it.
232+
if (!taskQueue.IsEmpty)
233+
{
234+
TryStartDrain();
235+
}
236+
}
237+
}
238+
}
161239
}

0 commit comments

Comments
 (0)