skip to Main Content

I have a System.Threading.Timer that receives some data each second, and I want its callback to put the received data into an object, which should be returned to client code. I tried to implement that using C#’s reference types mechanism. Here’s the current callback:

private void Receive(object? obj) {
  var data = GetData();

  obj = (object)data; // Does nothing
}

And here’s the method that starts the timer (timer is a field):

public void Start(Image image) {
  timer = new Timer(
    Receive,
    image,
    TimeSpan.FromSeconds(1),
    TimeSpan.FromSeconds(1)
  );
}

Expected behavior: each second the data (which is image data) is received and put to the image, the reference to which is specified in parameter image.

Actual behavior: when I defined a variable of type Image, passed it to the Start method and checked its value (using the Visual Studio debugger) after 5 seconds, it was the empty Bitmap I assigned to it at the very start.

I have also checked that the data (containing non-empty image) is sent properly.

Can anybody tell me what’s the issue, or, alternatively, suggest a different way to change the "state" object in a timer callback?

2

Answers


  1. In the Recieve method you get an obj as input reference. Then you try to set a value to obj. This last step cannot work, since you work on the reference.
    The only way it could work is, if the input reference is given as a ref, which is

    private void Receive(ref object? obj) {}
    

    Passing a reference type by reference enables the called method to replace the object to which the reference parameter refers in the caller.

    Login or Signup to reply.
  2. obj = (object)data; only sets the data locally in the Receive method. You could fix it by creating a wrapper object

    public class ImageData
    {
        public Image Image { get; set; }
    }
    

    Then create a static instance of it that can be accessed from where you need it

    public static readonly ImageData Data = new ImageData();
    

    Depending on where you must access the images it can also an instance member and be private; however, it must always be the same instance that you used to start the timer.

    Then start like this

    public void Start() {
      timer = new Timer(
        Receive,
        Data,
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(1)
      );
    }
    

    And receive

    private void Receive(object? obj) {
      var data = (ImageData)obj;  
      data.Image = GetData();
    }
    

    Now, you can access the new images through the static Data.Image property.


    Alternatively, you could "consume" the image (i.e., display or whatever you are doing with it) directly in the Receive callback and ignore the obj parameter. However, I do not have enough context information (where do the different code pieces reside and in which thread are they executed, etc.).


    Update

    The Image will be null in ImageData first, until the Receiver callback is called for the first time.

    If you want an image at the very start, you can do several things.

    1. Start the timer with the first time argument as 0 (this is the time it waits until firing the first time).

    2. Call the callback directly in Start: Receive(Data).

    3. Assign an image with Data.Image = GetData();.

    4. Initialize the image in ImageData with a dummy image (maybe some kind of wait image).


    Update 2

    Apparently you are creating a library. I assume the library will pull images regularly and make them available to a consumer.

    I suggest exposing an event in the library any consumer can subscribe to to receive images.

    In the library we declare event arguments as

    public class UpdateImageEventArgs : EventArgs
    {
        public UpdateImageEventArgs(Image image)
        {
            Image = image;
        }
    
        public Image Image { get; }
    }
    

    A possible implementation of the library:

    public class Library : IDisposable
    {
        public event EventHandler<UpdateImageEventArgs>? ImageUpdated;
    
        private System.Threading.Timer? _timer;
    
        public void Start()
        {
            _timer = new System.Threading.Timer(Receive, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
        }
    
        private void Receive(object? obj)
        {
            var image = GetImage();
            ImageUpdated?.Invoke(this, new UpdateImageEventArgs(image));
        }
    
        private static Image GetImage()
        {
            // TODO: Add your implementation
            throw new NotImplementedException();
        }
    
        public void Dispose()
        {
            if (_timer != null) {
                _timer.Dispose();
                _timer = null;
            }
        }
    }
    

    The consumer code:

    public static void Test()
    {
        var library = new Library();
        library.ImageUpdated += Library_ImageUpdated;
        library.Start();
    }
    
    private static void Library_ImageUpdated(object? sender, UpdateImageEventArgs e)
    {
        DoSomethingWith(e.Image);
    }
    

    If you want to stop the timer, you can do so by calling library.Dispose();. This also ensures that the timer resources are disposed properly.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search