You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 23:15:29