ImageAnimator.StopAnimate() doesn't appear to actually do anything
I'm working with the ImageAnimator object for the first time and finding that a call to StopAnimate() doesn't appear to actually stop the underlying thread from running and calling the FrameChanged handler.
I've created a test to illustrate this and I'm wondering if anyone with ImageAnimator experience can shed a little light on just WHAT the StopAnimate() method is actually supposed to do. MSDN says it should stop the animation, but it doesn'开发者_如何学运维t.
I realize I can use a bool to control when the updates are handled or not, but his is a workaround and makes the (if it worked) StopAnimate() method redundant.
Here is some testing code, no external resources necessary:
using System;
using System.IO;
using System.Threading;
using System.Drawing;
namespace ImageAnimatorDemo
{
class Program
{
static void Main(string[] args)
{
AnimateGif();
}
private static void AnimateGif()
{
Image testImage = null;
using (MemoryStream stream = new MemoryStream(Convert.FromBase64String(GifData)))
{
testImage = Image.FromStream(stream);
}
ImageAnimator.Animate(testImage, delegate(object o, EventArgs args)
{
// Each time the frame is updated this will fire
Console.WriteLine("Tick");
});
Thread.Sleep(750);
// The following code should stop the "animation" for the supplied image which I would
// THINK would stop the frame changed callback from firing...
Console.WriteLine("Stop requested, should not see more \"Ticks\"...");
ImageAnimator.StopAnimate(testImage, delegate(object o, EventArgs args)
{
Console.WriteLine("Stopped");
});
Console.ReadLine();
}
private static string GifData =
"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";
}
}
I decompiled the class and found that the ImageAnimator starts the thread worker:
/// <devdoc>
/// Worker thread procedure which implements the main animation loop.
/// NOTE: This is the ONLY code the worker thread executes, keeping it in one method helps better understand
/// any synchronization issues.
/// WARNING: Also, this is the only place where ImageInfo objects (not the contained image object) are modified,
/// so no access synchronization is required to modify them.
/// </devdoc>
[SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals")]
static void AnimateImages50ms() {
Debug.Assert(imageInfoList != null, "Null images list");
while( true ) {
// Acquire reader-lock to access imageInfoList, elemens in the list can be modified w/o needing a writer-lock.
// Observe that we don't need to check if the thread is waiting or a writer lock here since the thread this
// method runs in never acquires a writer lock.
rwImgListLock.AcquireReaderLock(Timeout.Infinite);
try {
for (int i=0;i < imageInfoList.Count; i++) {
ImageInfo imageInfo = imageInfoList[i];
// Frame delay is measured in 1/100ths of a second. This thread
// sleeps for 50 ms = 5/100ths of a second between frame updates,
// so we increase the frame delay count 5/100ths of a second
// at a time.
//
imageInfo.FrameTimer += 5;
if (imageInfo.FrameTimer >= imageInfo.FrameDelay(imageInfo.Frame)) {
imageInfo.FrameTimer = 0;
if (imageInfo.Frame + 1 < imageInfo.FrameCount) {
imageInfo.Frame++;
}
else {
imageInfo.Frame = 0;
}
if( imageInfo.FrameDirty ){
anyFrameDirty = true;
}
}
}
}
finally {
rwImgListLock.ReleaseReaderLock();
}
Thread.Sleep(50);
}
}
Once it starts it doesn't look like there is any way for it to stop, pretty crappy actually.
I had an issue where a PictureBox control with an animated gif Image would cause a hang on application exit.
The culprit was the static ImageAnimator class creating a thread that is in a while(true) loop.
Luckily, the ImageAnimator sets the thread used to a private field. It can be accessed with reflection and aborted, also stopping animation.
Here's my code that I've confirmed aborts the thread, animation stops.
try
{
var type = typeof(ImageAnimator);
var info = type.GetField("animationThread", BindingFlags.NonPublic | BindingFlags.Static);
if (info != null)
{
var value = info.GetValue(null);
var thread = value as Thread;
if (thread != null && thread.IsAlive)
{
thread.Abort();
}
}
}
catch{}
The reason your delegate doesn't get unsubscribed is because ImageAnimator
is comparing the delegate address to the address of the delegate you originally subscribed with. The delegate you passed to StopAnimate
is not the same delegate you passed to Animate
.
This is the relevant chunk of code in StopAnimate
:
try {
// Find the corresponding reference and remove it
for(int i = 0; i < imageInfoList.Count; i++) {
ImageInfo imageInfo = imageInfoList[i];
if(image == imageInfo.Image) {
if((onFrameChangedHandler == imageInfo.FrameChangedHandler) || (onFrameChangedHandler != null && onFrameChangedHandler.Equals(imageInfo.FrameChangedHandler))) {
imageInfoList.Remove(imageInfo);
}
break;
}
}
}
Note the comparison of onFrameChangedHandler
.
ImageAnimator
uses a single thread so all animated images in your application can be animated by a single thread. If you kill the worker thread all animated images in your app will stop animating. This might be fine if you only have one but is probably not what you want, in most cases.
Animate
and StopAnimate
work fine; you just need to call them correctly.
精彩评论