System.Threading.Timer回调无法修改状态对象?求原因及解决方法
问题原因
- C# 中,
Receive方法的object? obj参数是引用的副本(值传递引用)。你在回调里执行obj = (object)data,只是修改了这个局部变量的引用指向,完全不会影响外部传入的原始Image对象。 - 另外,
Image(比如Bitmap)属于不可变实例类型,直接替换引用无法更新外部的原始对象,必须通过修改对象内部数据或使用包装类持有可变引用来实现。
解决方案
方案1:用包装类持有可变引用
定义一个简单的包装类,用来存放可更新的Image引用,回调中修改包装类的内部属性,外部就能拿到最新值:
public class ImageWrapper { public Image? CurrentImage { get; set; } }
修改Start方法和回调逻辑:
private void Receive(object? obj) { var wrapper = obj as ImageWrapper; if (wrapper == null) return; var newImage = GetData(); // 假设GetData返回Image类型 // 记得释放旧图像资源,避免内存泄漏 wrapper.CurrentImage?.Dispose(); wrapper.CurrentImage = newImage; } public void Start(ImageWrapper imageWrapper) { timer = new Timer( Receive, imageWrapper, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1) ); }
外部调用示例:
var imageWrapper = new ImageWrapper { CurrentImage = new Bitmap(640, 480) }; yourInstance.Start(imageWrapper); // 后续通过imageWrapper.CurrentImage获取最新图像
方案2:直接修改现有Image对象的内容(如果数据是像素级的)
如果GetData返回的是像素数组、字节流这类原始图像数据,不需要替换整个Image对象,直接修改传入的Bitmap内部数据即可:
private void Receive(object? obj) { var targetBitmap = obj as Bitmap; if (targetBitmap == null) return; byte[] pixelData = GetPixelData(); // 假设返回对应格式的像素数组 Rectangle imageRect = new Rectangle(0, 0, targetBitmap.Width, targetBitmap.Height); // 锁定Bitmap内存进行写入 BitmapData bmpData = targetBitmap.LockBits( imageRect, ImageLockMode.WriteOnly, targetBitmap.PixelFormat); IntPtr dataPtr = bmpData.Scan0; int byteCount = Math.Abs(bmpData.Stride) * targetBitmap.Height; // 将像素数据复制到Bitmap内存 System.Runtime.InteropServices.Marshal.Copy(pixelData, 0, dataPtr, byteCount); targetBitmap.UnlockBits(bmpData); }
这种方式直接操作原始Bitmap的内存,外部传入的对象会实时更新,无需替换引用。
方案3:使用线程安全的类成员变量
如果不需要通过参数传递图像对象,可以将Image作为类的成员变量,同时加锁保证线程安全(Timer回调在线程池线程执行,外部读取时需避免并发问题):
private readonly object _imageSyncLock = new object(); private Image? _currentImage; private Timer? _timer; private void Receive(object? obj) { var newImage = GetData(); lock (_imageSyncLock) { // 释放旧资源 _currentImage?.Dispose(); _currentImage = newImage; } } public void Start() { _timer = new Timer( Receive, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1) ); } // 外部获取最新图像的方法 public Image? GetLatestImage() { lock (_imageSyncLock) { // 返回副本避免外部修改或线程冲突 return _currentImage?.Clone() as Image; } }
内容的提问来源于stack exchange,提问作者SNBS
相关产品推荐
相关产品推荐

